Make alphafold triangular inequality rule animation clip using this references

I'll read the PDF skill and the reference PDFs to understand the AlphaFold triangular inequality content before creating the animation.
Reading File
Now let me read all three PDFs in parallel to extract the triangular inequality content:
Running Command

cd /home/daytona/workspace/attachments/fb0bf012-9624-47a8-a5a3-c37526c10ced && python3 -c " from pypdf import PdfReader import re files = [ 'The brilliance of AlphaFold 3.pdf', 'What Is AlphaFold NEJM.pdf', 'how AlphaFold actually works.pdf' ] for f in files: print(f'=== {f} ===') reader = PdfReader(f) full_text = '' for page in reader.pages: full_text += page.extract_text() + '\n' # Search for triangular inequality mentions lines = full_text.split('\n') for i, line in enumerate(lines): if 'triangl' in line.lower() or 'distance' in line.lower() or 'constraint' in line.lower() or 'geometry' in line.lower(): start = max(0, i-2) end = min(len(lines), i+3) print(f' [Line {i}]', ' | '.join(lines[start:end])) print() "

Excellent! I have all the content I need. Now let me build the animation using Python with matplotlib and ffmpeg:
Running Command

mkdir -p /home/daytona/workspace/alphafold-animation && python3 -c "import matplotlib; import numpy; print('OK')"

Writing File

~/alphafold-animation/animate.py

"""
AlphaFold Triangular Inequality Animation
Based on:
  - "how AlphaFold actually works" (triangular attention explanation)
  - "What Is AlphaFold" NEJM (triangle inequality theorem in pair representation)
  - "The brilliance of AlphaFold 3" (triangles as protein backbone)

Scenes:
  0  Title card
  1  Pair representation intro — amino acid nodes + distance edges
  2  Standard attention VIOLATION — A-B updated ignoring C, inequality broken (red flash)
  3  Triangle inequality rule — d(A,B) <= d(A,C) + d(C,B), geometric proof
  4  Triangular attention FIX — attention over all pairs involving the edge, green tick
  5  AlphaFold context — how this bakes consistent geometry into the model
  6  Outro / summary card
"""

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as pe
from matplotlib.animation import FFMpegWriter
from matplotlib.patches import FancyArrowPatch, Arc, FancyBboxPatch, Circle
import matplotlib.transforms as transforms
import math

# ── palette ────────────────────────────────────────────────────────────────────
BG      = "#0d1117"
BLUE    = "#4fc3f7"
CYAN    = "#26c6da"
GREEN   = "#69f0ae"
RED     = "#ef5350"
ORANGE  = "#ffa726"
PURPLE  = "#ce93d8"
YELLOW  = "#fff176"
WHITE   = "#e8eaf6"
GREY    = "#546e7a"
DARKBLUE= "#1565c0"

FPS  = 30
W, H = 1280, 720

fig, ax = plt.subplots(figsize=(W/100, H/100), dpi=100)
fig.patch.set_facecolor(BG)

writer = FFMpegWriter(fps=FPS, metadata={"title": "AlphaFold Triangular Inequality"},
                      codec="libx264", extra_args=["-pix_fmt", "yuv420p", "-crf", "18"])

output_path = "/home/daytona/workspace/alphafold-animation/alphafold_triangular_inequality.mp4"

# ── helpers ────────────────────────────────────────────────────────────────────
def clear(ax):
    ax.cla()
    ax.set_facecolor(BG)
    ax.set_xlim(0, 16)
    ax.set_ylim(0, 9)
    ax.axis("off")

def node(ax, xy, label, color=BLUE, r=0.38, fontsize=14, zorder=8):
    c = Circle(xy, r, color=color, zorder=zorder)
    ax.add_patch(c)
    ax.text(xy[0], xy[1], label, ha="center", va="center",
            fontsize=fontsize, fontweight="bold", color=BG, zorder=zorder+1)

def edge(ax, p1, p2, color=GREY, lw=2, zorder=3, alpha=1.0, style="-"):
    ax.plot([p1[0], p2[0]], [p1[1], p2[1]], color=color, lw=lw,
            zorder=zorder, alpha=alpha, linestyle=style)

def edge_label(ax, p1, p2, txt, color=WHITE, offset=(0, 0.28), fontsize=11):
    mx, my = (p1[0]+p2[0])/2 + offset[0], (p1[1]+p2[1])/2 + offset[1]
    ax.text(mx, my, txt, ha="center", va="center", fontsize=fontsize,
            color=color, fontweight="bold",
            path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

def title_text(ax, txt, y=8.3, fontsize=22, color=CYAN):
    ax.text(8, y, txt, ha="center", va="center", fontsize=fontsize,
            color=color, fontweight="bold",
            path_effects=[pe.withStroke(linewidth=4, foreground=BG)])

def body_text(ax, lines, x=8, y_start=7.5, dy=0.55, fontsize=12, color=WHITE):
    for i, l in enumerate(lines):
        ax.text(x, y_start - i*dy, l, ha="center", va="center",
                fontsize=fontsize, color=color,
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

def badge(ax, xy, txt, color=GREEN, fontsize=11):
    ax.text(xy[0], xy[1], txt, ha="center", va="center",
            fontsize=fontsize, color=BG, fontweight="bold",
            bbox=dict(boxstyle="round,pad=0.4", facecolor=color, edgecolor="none", zorder=15))

def highlight_box(ax, x, y, w, h, color=GREEN, alpha=0.12, zorder=2):
    r = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1",
                       facecolor=color, edgecolor=color, linewidth=2,
                       alpha=alpha, zorder=zorder)
    ax.add_patch(r)

# node positions (consistent across scenes)
A = (4.5,  5.2)
B = (11.5, 5.2)
C = (8.0,  2.2)

def draw_abc_triangle(ax, alpha_ab=1, alpha_ac=1, alpha_bc=1,
                      col_ab=GREY, col_ac=GREY, col_bc=GREY,
                      lw=2.5, show_nodes=True,
                      node_colors=(BLUE, BLUE, BLUE)):
    edge(ax, A, B, color=col_ab, lw=lw, alpha=alpha_ab)
    edge(ax, A, C, color=col_ac, lw=lw, alpha=alpha_ac)
    edge(ax, B, C, color=col_bc, lw=lw, alpha=alpha_bc)
    if show_nodes:
        node(ax, A, "A", color=node_colors[0])
        node(ax, B, "B", color=node_colors[1])
        node(ax, C, "C", color=node_colors[2])

# ── total frame budget ─────────────────────────────────────────────────────────
SCENE_FRAMES = {
    "title":        90,   # 3 s
    "pair_repr":   120,   # 4 s
    "violation":   120,   # 4 s
    "theorem":     150,   # 5 s
    "tri_attn":    150,   # 5 s
    "context":     120,   # 4 s
    "outro":        90,   # 3 s
}
TOTAL = sum(SCENE_FRAMES.values())
print(f"Total frames: {TOTAL}  ({TOTAL/FPS:.1f} s)")

# ── easing ─────────────────────────────────────────────────────────────────────
def ease(t):
    """smooth-step"""
    t = max(0.0, min(1.0, t))
    return t * t * (3 - 2*t)

def lerp(a, b, t):
    return a + (b-a)*t

# ── render ─────────────────────────────────────────────────────────────────────
with writer.saving(fig, output_path, dpi=100):

    # ── SCENE 0: TITLE ──────────────────────────────────────────────────────────
    total_f = SCENE_FRAMES["title"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        alpha = ease(t * 2) if t < 0.5 else 1.0
        # decorative faint triangle
        for lw, a in [(8, 0.05), (4, 0.08), (2, 0.12)]:
            edge(ax, A, B, color=CYAN,   lw=lw, alpha=a*alpha)
            edge(ax, A, C, color=CYAN,   lw=lw, alpha=a*alpha)
            edge(ax, B, C, color=CYAN,   lw=lw, alpha=a*alpha)
        draw_abc_triangle(ax, col_ab=CYAN, col_ac=CYAN, col_bc=CYAN,
                          lw=2, node_colors=(CYAN, CYAN, CYAN))
        ax.text(8, 7.8, "AlphaFold & the Triangular Inequality",
                ha="center", va="center", fontsize=24, color=WHITE,
                fontweight="bold", alpha=alpha,
                path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
        ax.text(8, 7.1, "How triangular attention keeps protein geometry consistent",
                ha="center", va="center", fontsize=14, color=CYAN, alpha=alpha,
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
        ax.text(8, 0.55,
                "Sources: 'how AlphaFold actually works' | NEJM 'What Is AlphaFold' | 'The brilliance of AlphaFold 3'",
                ha="center", va="center", fontsize=8.5, color=GREY, alpha=alpha)
        writer.grab_frame()

    # ── SCENE 1: PAIR REPRESENTATION ────────────────────────────────────────────
    total_f = SCENE_FRAMES["pair_repr"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        title_text(ax, "Pair Representation in AlphaFold", y=8.4, fontsize=20)

        # fade-in edges then nodes
        e_alpha = ease(min(1, t * 3))
        n_alpha = ease(max(0, t*3 - 1))

        edge(ax, A, B, color=BLUE,   lw=2.5, alpha=e_alpha)
        edge(ax, A, C, color=BLUE,   lw=2.5, alpha=e_alpha)
        edge(ax, B, C, color=BLUE,   lw=2.5, alpha=e_alpha)

        if n_alpha > 0.05:
            node(ax, A, "A", color=BLUE)
            node(ax, B, "B", color=BLUE)
            node(ax, C, "C", color=BLUE)

        edge_label(ax, A, B, "d(A,B)", color=YELLOW,  offset=(0,  0.35))
        edge_label(ax, A, C, "d(A,C)", color=ORANGE,  offset=(-0.55, -0.1))
        edge_label(ax, B, C, "d(B,C)", color=ORANGE,  offset=( 0.55, -0.1))

        body_text(ax, [
            "Every amino-acid pair (i, j) stores a learned representation",
            "encoding distances and angles between residues.",
            "",
            '"...the pair representation might encode distances between",',
            '"each of the amino acids, or the distances and angles relative',
            ' to each other."  — how AlphaFold actually works',
        ], y_start=7.6, dy=0.48, fontsize=11, color=WHITE)
        writer.grab_frame()

    # ── SCENE 2: VIOLATION ──────────────────────────────────────────────────────
    total_f = SCENE_FRAMES["violation"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        title_text(ax, "Problem: Standard Attention Violates Geometry", y=8.4,
                   fontsize=18, color=RED)

        # Standard attention: only looks at A-B, ignores triangle
        pulse = 0.5 + 0.5*math.sin(t * math.pi * 6)  # pulsating
        if t < 0.4:
            # show all edges normally first
            draw_abc_triangle(ax, col_ab=WHITE, col_ac=GREY, col_bc=GREY, lw=2.5)
            edge_label(ax, A, B, "A↔B updated separately", color=YELLOW, offset=(0, 0.42), fontsize=11)
        else:
            # highlight violation
            viol_alpha = ease((t - 0.4) / 0.3)
            draw_abc_triangle(ax, col_ab=RED, col_ac=GREY, col_bc=GREY,
                              lw=3 + pulse, alpha_ab=1)
            # draw big X over the triangle
            x_alpha = ease(max(0, (t - 0.55) / 0.25))
            for dx, dy in [(-0.6, -0.6), (0.6, 0.6)]:
                ax.annotate("", xy=(8+dx, 4.1+dy), xytext=(8-dx, 4.1-dy),
                            arrowprops=dict(arrowstyle="-", color=RED,
                                           lw=4, alpha=x_alpha))

            # false distance label
            ax.text(8, 4.1, "✗", ha="center", va="center", fontsize=60,
                    color=RED, alpha=x_alpha*0.7, zorder=10)

            edge_label(ax, A, C, "d(A,C)", color=GREY,   offset=(-0.55,-0.1))
            edge_label(ax, B, C, "d(B,C)", color=GREY,   offset=( 0.55,-0.1))

        body_text(ax, [
            '"Standard attention on pairs A-B updates d(A,B)',
            " separately from d(A,C) and d(B,C).",
            " This can produce d(A,B) > d(A,C) + d(C,B) — impossible in real space!\"",
            "  — how AlphaFold actually works",
        ], y_start=1.8, dy=0.48, fontsize=11, color=WHITE)
        writer.grab_frame()

    # ── SCENE 3: TRIANGLE INEQUALITY THEOREM ────────────────────────────────────
    total_f = SCENE_FRAMES["theorem"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        title_text(ax, "The Triangle Inequality Theorem", y=8.45, fontsize=20, color=YELLOW)

        # Draw clean triangle
        draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN, lw=3,
                          node_colors=(CYAN, CYAN, GREEN))

        # Animate the inequality writing
        # Phase 1 (0-0.3): show d(A,C) + d(C,B) label
        # Phase 2 (0.3-0.6): show >= d(A,B) label
        # Phase 3 (0.6-1.0): show the rule box

        t1 = ease(min(1, t / 0.3))
        t2 = ease(max(0, (t - 0.3) / 0.3))
        t3 = ease(max(0, (t - 0.65) / 0.3))

        edge_label(ax, A, B, "d(A,B)", color=CYAN,  offset=(0,  0.40), fontsize=13)
        edge_label(ax, A, C, "d(A,C)", color=GREEN, offset=(-0.55,-0.1), fontsize=13)
        edge_label(ax, B, C, "d(C,B)", color=GREEN, offset=( 0.55,-0.1), fontsize=13)

        # inequality text
        if t1 > 0.05:
            ax.text(8, 7.15, "d(A,C)  +  d(C,B)", ha="center", va="center",
                    fontsize=15, color=GREEN, alpha=t1, fontweight="bold",
                    path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
        if t2 > 0.05:
            ax.text(8, 6.65, "≥   d(A,B)", ha="center", va="center",
                    fontsize=15, color=CYAN, alpha=t2, fontweight="bold",
                    path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

        if t3 > 0.05:
            highlight_box(ax, 2.5, 6.3, 11, 1.1, color=YELLOW, alpha=0.13*t3)
            ax.text(8, 6.85,
                    "The sum of any two sides of a triangle\n"
                    "is always ≥ the third side.",
                    ha="center", va="center", fontsize=13,
                    color=YELLOW, alpha=t3, fontweight="bold",
                    path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

        # source quote
        body_text(ax, [
            '"...the triangle inequality theorem, where the sum of two edges',
            ' on a triangle must be equal to or greater than the third."',
            "  — What Is AlphaFold, NEJM",
        ], y_start=1.65, dy=0.48, fontsize=11, color=GREY)
        writer.grab_frame()

    # ── SCENE 4: TRIANGULAR ATTENTION FIX ───────────────────────────────────────
    total_f = SCENE_FRAMES["tri_attn"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        title_text(ax, "Fix: Triangular Attention", y=8.45, fontsize=20, color=GREEN)

        # Phase 1 (0-0.35): show all edges, highlight A-B
        # Phase 2 (0.35-0.7): show update arrow looping through C
        # Phase 3 (0.7-1.0): show green tick + label

        t1 = ease(min(1, t / 0.35))
        t2 = ease(max(0, (t-0.35)/0.35))
        t3 = ease(max(0, (t-0.70)/0.30))

        draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN, lw=2.5,
                          node_colors=(CYAN, CYAN, GREEN))

        edge_label(ax, A, B, "d(A,B)",  color=CYAN,  offset=(0,   0.40), fontsize=13)
        edge_label(ax, A, C, "d(A,C)",  color=GREEN, offset=(-0.55,-0.1), fontsize=13)
        edge_label(ax, B, C, "d(C,B)",  color=GREEN, offset=( 0.55,-0.1), fontsize=13)

        if t2 > 0.05:
            # Animated "attention flow" arrow: A -> C -> B, updating A-B
            # draw curved arc from A to C to B
            prog = t2
            # arc from A through C to B
            pts_x = [A[0], C[0], B[0]]
            pts_y = [A[1], C[1], B[1]]
            n = max(2, int(prog * 40))
            xs = np.linspace(A[0], B[0], n)
            # quadratic bezier through C
            ys = []
            for i_pt in range(n):
                s = i_pt/(max(n-1,1))
                # quadratic bezier
                y_b = (1-s)**2*A[1] + 2*(1-s)*s*C[1] + s**2*B[1]
                ys.append(y_b)
            ax.plot(xs, ys, color=ORANGE, lw=3, alpha=0.9*t2, zorder=6,
                    linestyle="--")
            # arrowhead at current progress point
            idx = min(n-1, int(prog * (n-1)))
            if idx > 0:
                ax.annotate("", xy=(xs[min(idx, n-1)], ys[min(idx, n-1)]),
                            xytext=(xs[max(idx-1,0)], ys[max(idx-1,0)]),
                            arrowprops=dict(arrowstyle="-|>", color=ORANGE,
                                           lw=2, mutation_scale=18),
                            zorder=7)

            ax.text(8, 3.9 - 0.3*t2,
                    "d(A,B) update attends to ALL pairs involving A and B\n(i.e. A-C and B-C are consulted too)",
                    ha="center", va="center", fontsize=12,
                    color=ORANGE, alpha=t2, fontweight="bold",
                    path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

        if t3 > 0.05:
            ax.text(8, 7.4, "✓  Triangular Attention", ha="center", va="center",
                    fontsize=18, color=GREEN, alpha=t3, fontweight="bold",
                    path_effects=[pe.withStroke(linewidth=4, foreground=BG)])

        body_text(ax, [
            '"...instead of looking at all pairs separately, get the attention mechanism',
            " to pay attention to all pairs involving A and B.",
            ' This is called triangular attention." — how AlphaFold actually works',
        ], y_start=1.65, dy=0.48, fontsize=11, color=GREY)
        writer.grab_frame()

    # ── SCENE 5: CONTEXT IN ALPHAFOLD ───────────────────────────────────────────
    total_f = SCENE_FRAMES["context"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        title_text(ax, "Why This Matters for Protein Structure", y=8.45,
                   fontsize=18, color=PURPLE)

        # draw mini protein backbone as series of amino-acid triangles
        n_aa = 6
        cx0, cy0 = 2.0, 4.8
        spacing = 2.0
        color_cycle = [BLUE, CYAN, GREEN, YELLOW, ORANGE, PURPLE]
        t_show = ease(min(1, t * 2.5))
        for i in range(n_aa):
            show_alpha = ease(max(0, t_show - i*0.15))
            if show_alpha < 0.02:
                continue
            cx = cx0 + i*spacing
            cy = cy0 + (0.3 if i%2==0 else -0.3)
            col = color_cycle[i % len(color_cycle)]
            # small triangle representing amino acid backbone
            ta = np.array([cx-0.3, cy+0.35])
            tb = np.array([cx+0.3, cy+0.35])
            tc = np.array([cx,     cy-0.25])
            for p1, p2 in [(ta,tb),(tb,tc),(tc,ta)]:
                ax.plot([p1[0],p2[0]], [p1[1],p2[1]], color=col,
                        lw=2, alpha=show_alpha*0.9)
            ax.text(cx, cy+0.08, str(i+1), ha="center", va="center",
                    fontsize=9, color=col, alpha=show_alpha, fontweight="bold")
            # connect to next with dashed line
            if i < n_aa-1:
                nx = cx0 + (i+1)*spacing
                ny = cy0 + (0.3 if (i+1)%2==0 else -0.3)
                ax.plot([cx+0.3, nx-0.3], [cy+0.35, ny+0.35],
                        color=GREY, lw=1.5, linestyle="--", alpha=show_alpha*0.6)

        t3 = ease(max(0, (t-0.35)/0.45))
        body_text(ax, [
            "Each amino acid acts as a rigid triangle in 3D space.",
            "Triangular attention ensures the pair representation encodes",
            "distances that are geometrically consistent — no impossible gaps.",
            "",
            '"...the basic idea is to treat each amino acid as if it\'s a triangle."',
            "  — how AlphaFold actually works",
        ], y_start=7.7, dy=0.52, fontsize=11.5, color=WHITE)
        writer.grab_frame()

    # ── SCENE 6: OUTRO ──────────────────────────────────────────────────────────
    total_f = SCENE_FRAMES["outro"]
    for f in range(total_f):
        t = f / total_f
        clear(ax)
        alpha_out = 1.0 - ease(max(0, (t-0.75)/0.25))

        draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN,
                          lw=2, node_colors=(CYAN, CYAN, GREEN))

        ax.text(8, 8.1, "Summary", ha="center", va="center",
                fontsize=22, color=WHITE, fontweight="bold", alpha=alpha_out,
                path_effects=[pe.withStroke(linewidth=5, foreground=BG)])

        bullets = [
            (GREEN,  "Pair representation stores distances/angles between all residue pairs"),
            (RED,    "Standard attention can violate the triangle inequality"),
            (YELLOW, "Triangle inequality: d(A,B) ≤ d(A,C) + d(C,B)"),
            (CYAN,   "Triangular attention consults all pairs sharing an endpoint"),
            (PURPLE, "Result: geometrically consistent, physically plausible structures"),
        ]
        for i, (col, txt) in enumerate(bullets):
            t_b = ease(max(0, t*6 - i))
            ax.text(2.0, 6.9 - i*0.75, "●", ha="left", va="center",
                    fontsize=13, color=col, alpha=t_b*alpha_out,
                    path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
            ax.text(2.55, 6.9 - i*0.75, txt, ha="left", va="center",
                    fontsize=11.5, color=WHITE, alpha=t_b*alpha_out,
                    path_effects=[pe.withStroke(linewidth=2, foreground=BG)])

        ax.text(8, 0.55,
                "Sources: 'how AlphaFold actually works' | NEJM 'What Is AlphaFold' | 'The brilliance of AlphaFold 3'",
                ha="center", va="center", fontsize=8.5, color=GREY, alpha=alpha_out)
        writer.grab_frame()

print("Done!")
Running Command

cd /home/daytona/workspace/alphafold-animation && python3 animate.py 2>&1

The ffmpeg pipe fails with rgba input. Let me fix by saving frames to PNG first, then encoding:
Running Command

ffmpeg -version 2>&1 | head -3 && python3 -c "import matplotlib; print(matplotlib.__version__)"

Writing File

~/alphafold-animation/animate.py

"""
AlphaFold Triangular Inequality Animation
Saves frames to PNG files, then encodes with ffmpeg.
"""

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as pe
from matplotlib.patches import FancyBboxPatch, Circle
import os
import math
import subprocess

# ── palette ────────────────────────────────────────────────────────────────────
BG      = "#0d1117"
BLUE    = "#4fc3f7"
CYAN    = "#26c6da"
GREEN   = "#69f0ae"
RED     = "#ef5350"
ORANGE  = "#ffa726"
PURPLE  = "#ce93d8"
YELLOW  = "#fff176"
WHITE   = "#e8eaf6"
GREY    = "#546e7a"

FPS  = 30
W, H = 1280, 720

FRAMES_DIR = "/home/daytona/workspace/alphafold-animation/frames"
os.makedirs(FRAMES_DIR, exist_ok=True)

fig, ax = plt.subplots(figsize=(W/100, H/100), dpi=100)
fig.patch.set_facecolor(BG)

# ── helpers ────────────────────────────────────────────────────────────────────
def clear(ax):
    ax.cla()
    ax.set_facecolor(BG)
    ax.set_xlim(0, 16)
    ax.set_ylim(0, 9)
    ax.axis("off")

def node(ax, xy, label, color=BLUE, r=0.38, fontsize=14, zorder=8):
    c = Circle(xy, r, color=color, zorder=zorder)
    ax.add_patch(c)
    ax.text(xy[0], xy[1], label, ha="center", va="center",
            fontsize=fontsize, fontweight="bold", color=BG, zorder=zorder+1)

def edge(ax, p1, p2, color=GREY, lw=2, zorder=3, alpha=1.0, style="-"):
    ax.plot([p1[0], p2[0]], [p1[1], p2[1]], color=color, lw=lw,
            zorder=zorder, alpha=alpha, linestyle=style)

def edge_label(ax, p1, p2, txt, color=WHITE, offset=(0, 0.28), fontsize=11):
    mx, my = (p1[0]+p2[0])/2 + offset[0], (p1[1]+p2[1])/2 + offset[1]
    ax.text(mx, my, txt, ha="center", va="center", fontsize=fontsize,
            color=color, fontweight="bold",
            path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

def title_text(ax, txt, y=8.3, fontsize=22, color=CYAN):
    ax.text(8, y, txt, ha="center", va="center", fontsize=fontsize,
            color=color, fontweight="bold",
            path_effects=[pe.withStroke(linewidth=4, foreground=BG)])

def body_text(ax, lines, x=8, y_start=7.5, dy=0.55, fontsize=12, color=WHITE):
    for i, l in enumerate(lines):
        ax.text(x, y_start - i*dy, l, ha="center", va="center",
                fontsize=fontsize, color=color,
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])

def highlight_box(ax, x, y, w, h, color=GREEN, alpha=0.12, zorder=2):
    r = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1",
                       facecolor=color, edgecolor=color, linewidth=2,
                       alpha=alpha, zorder=zorder)
    ax.add_patch(r)

# node positions
A = (4.5,  5.2)
B = (11.5, 5.2)
C = (8.0,  2.2)

def draw_abc_triangle(ax, alpha_ab=1, alpha_ac=1, alpha_bc=1,
                      col_ab=GREY, col_ac=GREY, col_bc=GREY,
                      lw=2.5, show_nodes=True,
                      node_colors=(BLUE, BLUE, BLUE)):
    edge(ax, A, B, color=col_ab, lw=lw, alpha=alpha_ab)
    edge(ax, A, C, color=col_ac, lw=lw, alpha=alpha_ac)
    edge(ax, B, C, color=col_bc, lw=lw, alpha=alpha_bc)
    if show_nodes:
        node(ax, A, "A", color=node_colors[0])
        node(ax, B, "B", color=node_colors[1])
        node(ax, C, "C", color=node_colors[2])

def ease(t):
    t = max(0.0, min(1.0, t))
    return t * t * (3 - 2*t)

SCENE_FRAMES = {
    "title":        90,
    "pair_repr":   120,
    "violation":   120,
    "theorem":     150,
    "tri_attn":    150,
    "context":     120,
    "outro":        90,
}
TOTAL = sum(SCENE_FRAMES.values())
print(f"Total frames: {TOTAL}  ({TOTAL/FPS:.1f} s)")

frame_idx = 0

def save_frame():
    global frame_idx
    fig.savefig(f"{FRAMES_DIR}/frame_{frame_idx:05d}.png",
                dpi=100, facecolor=BG, bbox_inches=None)
    frame_idx += 1

# ── SCENE 0: TITLE ────────────────────────────────────────────────────────────
total_f = SCENE_FRAMES["title"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    alpha = ease(t * 2) if t < 0.5 else 1.0
    for lw, a in [(8, 0.05), (4, 0.08), (2, 0.12)]:
        edge(ax, A, B, color=CYAN, lw=lw, alpha=a*alpha)
        edge(ax, A, C, color=CYAN, lw=lw, alpha=a*alpha)
        edge(ax, B, C, color=CYAN, lw=lw, alpha=a*alpha)
    draw_abc_triangle(ax, col_ab=CYAN, col_ac=CYAN, col_bc=CYAN,
                      lw=2, node_colors=(CYAN, CYAN, CYAN))
    ax.text(8, 7.8, "AlphaFold & the Triangular Inequality",
            ha="center", va="center", fontsize=24, color=WHITE,
            fontweight="bold", alpha=alpha,
            path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
    ax.text(8, 7.1, "How triangular attention keeps protein geometry consistent",
            ha="center", va="center", fontsize=14, color=CYAN, alpha=alpha,
            path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
    ax.text(8, 0.55,
            "Sources: 'how AlphaFold actually works'  |  NEJM 'What Is AlphaFold'  |  'The brilliance of AlphaFold 3'",
            ha="center", va="center", fontsize=8.5, color=GREY, alpha=alpha)
    save_frame()

# ── SCENE 1: PAIR REPRESENTATION ─────────────────────────────────────────────
total_f = SCENE_FRAMES["pair_repr"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    title_text(ax, "Pair Representation in AlphaFold", y=8.4, fontsize=20)
    e_alpha = ease(min(1, t * 3))
    edge(ax, A, B, color=BLUE, lw=2.5, alpha=e_alpha)
    edge(ax, A, C, color=BLUE, lw=2.5, alpha=e_alpha)
    edge(ax, B, C, color=BLUE, lw=2.5, alpha=e_alpha)
    if e_alpha > 0.3:
        node(ax, A, "A", color=BLUE)
        node(ax, B, "B", color=BLUE)
        node(ax, C, "C", color=BLUE)
    edge_label(ax, A, B, "d(A,B)", color=YELLOW, offset=(0,   0.40))
    edge_label(ax, A, C, "d(A,C)", color=ORANGE, offset=(-0.55,-0.1))
    edge_label(ax, B, C, "d(B,C)", color=ORANGE, offset=( 0.55,-0.1))
    body_text(ax, [
        "Every amino-acid pair (i, j) stores a learned representation",
        "encoding distances and angles between residues.",
        "",
        '"...the pair representation might encode distances between each',
        ' of the amino acids, or the distances and angles relative to each other."',
        "  \u2014 how AlphaFold actually works",
    ], y_start=7.6, dy=0.48, fontsize=11, color=WHITE)
    save_frame()

# ── SCENE 2: VIOLATION ───────────────────────────────────────────────────────
total_f = SCENE_FRAMES["violation"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    title_text(ax, "Problem: Standard Attention Violates Geometry", y=8.4,
               fontsize=18, color=RED)
    pulse = 0.5 + 0.5*math.sin(t * math.pi * 6)
    if t < 0.4:
        draw_abc_triangle(ax, col_ab=WHITE, col_ac=GREY, col_bc=GREY, lw=2.5)
        edge_label(ax, A, B, "A\u2194B updated separately", color=YELLOW, offset=(0, 0.42), fontsize=11)
    else:
        draw_abc_triangle(ax, col_ab=RED, col_ac=GREY, col_bc=GREY,
                          lw=3 + pulse)
        x_alpha = ease(max(0, (t - 0.55) / 0.25))
        ax.text(8, 4.1, "\u2717", ha="center", va="center", fontsize=60,
                color=RED, alpha=x_alpha*0.7, zorder=10)
        edge_label(ax, A, C, "d(A,C)", color=GREY, offset=(-0.55,-0.1))
        edge_label(ax, B, C, "d(B,C)", color=GREY, offset=( 0.55,-0.1))
    body_text(ax, [
        '"Standard attention on pairs A-B updates d(A,B)',
        " separately from d(A,C) and d(B,C).",
        " This can produce d(A,B) > d(A,C)+d(C,B) \u2014 impossible in real space!\"",
        "  \u2014 how AlphaFold actually works",
    ], y_start=1.8, dy=0.48, fontsize=11, color=WHITE)
    save_frame()

# ── SCENE 3: TRIANGLE INEQUALITY THEOREM ────────────────────────────────────
total_f = SCENE_FRAMES["theorem"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    title_text(ax, "The Triangle Inequality Theorem", y=8.45, fontsize=20, color=YELLOW)
    draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN, lw=3,
                      node_colors=(CYAN, CYAN, GREEN))
    t1 = ease(min(1, t / 0.3))
    t2 = ease(max(0, (t - 0.3) / 0.3))
    t3 = ease(max(0, (t - 0.65) / 0.3))
    edge_label(ax, A, B, "d(A,B)", color=CYAN,  offset=(0,   0.40), fontsize=13)
    edge_label(ax, A, C, "d(A,C)", color=GREEN, offset=(-0.55,-0.1), fontsize=13)
    edge_label(ax, B, C, "d(C,B)", color=GREEN, offset=( 0.55,-0.1), fontsize=13)
    if t1 > 0.05:
        ax.text(8, 7.15, "d(A,C)  +  d(C,B)", ha="center", va="center",
                fontsize=15, color=GREEN, alpha=t1, fontweight="bold",
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
    if t2 > 0.05:
        ax.text(8, 6.65, "\u2265   d(A,B)", ha="center", va="center",
                fontsize=15, color=CYAN, alpha=t2, fontweight="bold",
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
    if t3 > 0.05:
        highlight_box(ax, 2.5, 6.3, 11, 1.1, color=YELLOW, alpha=0.13*t3)
        ax.text(8, 6.85,
                "The sum of any two sides of a triangle is always \u2265 the third side.",
                ha="center", va="center", fontsize=13,
                color=YELLOW, alpha=t3, fontweight="bold",
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
    body_text(ax, [
        '"...the triangle inequality theorem, where the sum of two edges',
        ' on a triangle must be equal to or greater than the third."',
        "  \u2014 What Is AlphaFold, NEJM",
    ], y_start=1.65, dy=0.48, fontsize=11, color=GREY)
    save_frame()

# ── SCENE 4: TRIANGULAR ATTENTION FIX ───────────────────────────────────────
total_f = SCENE_FRAMES["tri_attn"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    title_text(ax, "Fix: Triangular Attention", y=8.45, fontsize=20, color=GREEN)
    t1 = ease(min(1, t / 0.35))
    t2 = ease(max(0, (t-0.35)/0.35))
    t3 = ease(max(0, (t-0.70)/0.30))
    draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN, lw=2.5,
                      node_colors=(CYAN, CYAN, GREEN))
    edge_label(ax, A, B, "d(A,B)", color=CYAN,  offset=(0,   0.40), fontsize=13)
    edge_label(ax, A, C, "d(A,C)", color=GREEN, offset=(-0.55,-0.1), fontsize=13)
    edge_label(ax, B, C, "d(C,B)", color=GREEN, offset=( 0.55,-0.1), fontsize=13)
    if t2 > 0.05:
        prog = t2
        n = max(2, int(prog * 50))
        xs = np.linspace(A[0], B[0], n)
        ys = []
        for i_pt in range(n):
            s = i_pt / max(n-1, 1)
            y_b = (1-s)**2*A[1] + 2*(1-s)*s*C[1] + s**2*B[1]
            ys.append(y_b)
        ax.plot(xs, ys, color=ORANGE, lw=3, alpha=0.9*t2, zorder=6, linestyle="--")
        if n > 1:
            idx = min(n-1, int(prog*(n-1)))
            if idx > 0:
                ax.annotate("", xy=(xs[idx], ys[idx]),
                            xytext=(xs[max(idx-1,0)], ys[max(idx-1,0)]),
                            arrowprops=dict(arrowstyle="-|>", color=ORANGE,
                                           lw=2, mutation_scale=18),
                            zorder=7)
        ax.text(8, 3.7,
                "d(A,B) update attends to ALL pairs involving A and B\n"
                "(d(A,C) and d(B,C) are consulted together)",
                ha="center", va="center", fontsize=12,
                color=ORANGE, alpha=t2, fontweight="bold",
                path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
    if t3 > 0.05:
        ax.text(8, 7.4, "\u2713  Triangular Attention", ha="center", va="center",
                fontsize=18, color=GREEN, alpha=t3, fontweight="bold",
                path_effects=[pe.withStroke(linewidth=4, foreground=BG)])
    body_text(ax, [
        '"...instead of looking at all pairs separately, get the attention mechanism',
        " to pay attention to all pairs involving A and B.",
        ' This is called triangular attention." \u2014 how AlphaFold actually works',
    ], y_start=1.65, dy=0.48, fontsize=11, color=GREY)
    save_frame()

# ── SCENE 5: CONTEXT IN ALPHAFOLD ───────────────────────────────────────────
total_f = SCENE_FRAMES["context"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    title_text(ax, "Why This Matters for Protein Structure", y=8.45,
               fontsize=18, color=PURPLE)
    n_aa = 6
    cx0, cy0 = 2.0, 4.8
    spacing = 2.0
    color_cycle = [BLUE, CYAN, GREEN, YELLOW, ORANGE, PURPLE]
    t_show = ease(min(1, t * 2.5))
    for i in range(n_aa):
        show_alpha = ease(max(0, t_show - i*0.15))
        if show_alpha < 0.02:
            continue
        cx = cx0 + i*spacing
        cy = cy0 + (0.3 if i%2==0 else -0.3)
        col = color_cycle[i % len(color_cycle)]
        ta = np.array([cx-0.3, cy+0.35])
        tb = np.array([cx+0.3, cy+0.35])
        tc = np.array([cx,     cy-0.25])
        for p1, p2 in [(ta,tb),(tb,tc),(tc,ta)]:
            ax.plot([p1[0],p2[0]], [p1[1],p2[1]], color=col,
                    lw=2, alpha=show_alpha*0.9)
        ax.text(cx, cy+0.08, str(i+1), ha="center", va="center",
                fontsize=9, color=col, alpha=show_alpha, fontweight="bold")
        if i < n_aa-1:
            nx = cx0 + (i+1)*spacing
            ny = cy0 + (0.3 if (i+1)%2==0 else -0.3)
            ax.plot([cx+0.3, nx-0.3], [cy+0.35, ny+0.35],
                    color=GREY, lw=1.5, linestyle="--", alpha=show_alpha*0.6)
    body_text(ax, [
        "Each amino acid acts as a rigid triangle in 3D space.",
        "Triangular attention ensures the pair representation encodes",
        "distances that are geometrically consistent \u2014 no impossible gaps.",
        "",
        '"...the basic idea is to treat each amino acid as if it\'s a triangle."',
        "  \u2014 how AlphaFold actually works",
    ], y_start=7.7, dy=0.52, fontsize=11.5, color=WHITE)
    save_frame()

# ── SCENE 6: OUTRO ───────────────────────────────────────────────────────────
total_f = SCENE_FRAMES["outro"]
for f in range(total_f):
    t = f / total_f
    clear(ax)
    alpha_out = 1.0 - ease(max(0, (t-0.75)/0.25))
    draw_abc_triangle(ax, col_ab=CYAN, col_ac=GREEN, col_bc=GREEN,
                      lw=2, node_colors=(CYAN, CYAN, GREEN))
    ax.text(8, 8.1, "Summary", ha="center", va="center",
            fontsize=22, color=WHITE, fontweight="bold", alpha=alpha_out,
            path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
    bullets = [
        (GREEN,  "Pair representation stores distances/angles between all residue pairs"),
        (RED,    "Standard attention can violate the triangle inequality"),
        (YELLOW, "Triangle inequality: d(A,B) \u2264 d(A,C) + d(C,B)"),
        (CYAN,   "Triangular attention consults all pairs sharing an endpoint"),
        (PURPLE, "Result: geometrically consistent, physically plausible structures"),
    ]
    for i, (col, txt) in enumerate(bullets):
        t_b = ease(max(0, t*6 - i))
        ax.text(2.0, 6.9 - i*0.75, "\u25cf", ha="left", va="center",
                fontsize=13, color=col, alpha=t_b*alpha_out,
                path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
        ax.text(2.55, 6.9 - i*0.75, txt, ha="left", va="center",
                fontsize=11.5, color=WHITE, alpha=t_b*alpha_out,
                path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
    ax.text(8, 0.55,
            "Sources: 'how AlphaFold actually works'  |  NEJM 'What Is AlphaFold'  |  'The brilliance of AlphaFold 3'",
            ha="center", va="center", fontsize=8.5, color=GREY, alpha=alpha_out)
    save_frame()

plt.close(fig)
print(f"Saved {frame_idx} frames to {FRAMES_DIR}")

# Encode with ffmpeg
output_path = "/home/daytona/workspace/alphafold-animation/alphafold_triangular_inequality.mp4"
cmd = [
    "ffmpeg", "-y",
    "-framerate", str(FPS),
    "-i", f"{FRAMES_DIR}/frame_%05d.png",
    "-c:v", "libx264",
    "-pix_fmt", "yuv420p",
    "-crf", "18",
    "-preset", "fast",
    output_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
    import os
    size = os.path.getsize(output_path)
    print(f"Encoded successfully: {output_path} ({size/1024/1024:.1f} MB)")
else:
    print("FFMPEG ERROR:", result.stderr[-1000:])
Editing File

~/alphafold-animation/animate.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/animate.py'
Rather than patching the old file, let me just overwrite it entirely with the corrected version:
Running Command

cat > /home/daytona/workspace/alphafold-animation/animate.py << 'PYEOF' import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.patheffects as pe from matplotlib.patches import FancyBboxPatch, Circle import os, math, subprocess BG= "#0d1117" BLUE= "#4fc3f7" CYAN= "#26c6da" GREEN= "#69f0ae" RED= "#ef5350" ORANGE= "#ffa726" PURPLE= "#ce93d8" YELLOW= "#fff176" WHITE= "#e8eaf6" GREY= "#546e7a" FPS=30; W=1280; H=720 FRAMES_DIR="/home/daytona/workspace/alphafold-animation/frames" os.makedirs(FRAMES_DIR, exist_ok=True) fig, ax = plt.subplots(figsize=(W/100, H/100), dpi=100) fig.patch.set_facecolor(BG) def clear(ax): ax.cla(); ax.set_facecolor(BG) ax.set_xlim(0,16); ax.set_ylim(0,9); ax.axis("off") def node(ax,xy,label,color=BLUE,r=0.38,fontsize=14,zorder=8): ax.add_patch(Circle(xy,r,color=color,zorder=zorder)) ax.text(xy[0],xy[1],label,ha="center",va="center",fontsize=fontsize,fontweight="bold",color=BG,zorder=zorder+1) def edge(ax,p1,p2,color=GREY,lw=2,zorder=3,alpha=1.0,style="-"): ax.plot([p1[0],p2[0]],[p1[1],p2[1]],color=color,lw=lw,zorder=zorder,alpha=alpha,linestyle=style) def edge_label(ax,p1,p2,txt,color=WHITE,offset=(0,0.28),fontsize=11): mx,my=(p1[0]+p2[0])/2+offset[0],(p1[1]+p2[1])/2+offset[1] ax.text(mx,my,txt,ha="center",va="center",fontsize=fontsize,color=color,fontweight="bold", path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) def title_text(ax,txt,y=8.3,fontsize=22,color=CYAN): ax.text(8,y,txt,ha="center",va="center",fontsize=fontsize,color=color,fontweight="bold", path_effects=[pe.withStroke(linewidth=4,foreground=BG)]) def body_text(ax,lines,x=8,y_start=7.5,dy=0.55,fontsize=12,color=WHITE): for i,l in enumerate(lines): ax.text(x,y_start-i*dy,l,ha="center",va="center",fontsize=fontsize,color=color, path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) def highlight_box(ax,x,y,w,h,color=GREEN,alpha=0.12,zorder=2): ax.add_patch(FancyBboxPatch((x,y),w,h,boxstyle="round,pad=0.1", facecolor=color,edgecolor=color,linewidth=2,alpha=alpha,zorder=zorder)) A=(4.5,5.2); B=(11.5,5.2); C=(8.0,2.2) def draw_tri(ax,col_ab=GREY,col_ac=GREY,col_bc=GREY,lw=2.5, nc=(BLUE,BLUE,BLUE),a_ab=1,a_ac=1,a_bc=1): edge(ax,A,B,color=col_ab,lw=lw,alpha=a_ab) edge(ax,A,C,color=col_ac,lw=lw,alpha=a_ac) edge(ax,B,C,color=col_bc,lw=lw,alpha=a_bc) node(ax,A,"A",color=nc[0]); node(ax,B,"B",color=nc[1]); node(ax,C,"C",color=nc[2]) def ease(t): t=max(0.0,min(1.0,t)); return t*t*(3-2*t) SF={"title":90,"pair":120,"viol":120,"thm":150,"attn":150,"ctx":120,"outro":90} TOTAL=sum(SF.values()) print(f"Frames: {TOTAL} = {TOTAL/FPS:.1f}s") fidx=0 def save_f(): global fidx fig.savefig(f"{FRAMES_DIR}/frame_{fidx:05d}.png",dpi=100,facecolor=BG) fidx+=1 # TITLE for f in range(SF["title"]): t=f/SF["title"]; clear(ax) a=ease(t*2) if t<0.5 else 1.0 for lw2,aa in [(8,0.05),(4,0.08),(2,0.12)]: edge(ax,A,B,color=CYAN,lw=lw2,alpha=aa*a) edge(ax,A,C,color=CYAN,lw=lw2,alpha=aa*a) edge(ax,B,C,color=CYAN,lw=lw2,alpha=aa*a) draw_tri(ax,col_ab=CYAN,col_ac=CYAN,col_bc=CYAN,lw=2,nc=(CYAN,CYAN,CYAN)) ax.text(8,7.8,"AlphaFold & the Triangular Inequality",ha="center",va="center", fontsize=24,color=WHITE,fontweight="bold",alpha=a, path_effects=[pe.withStroke(linewidth=5,foreground=BG)]) ax.text(8,7.1,"How triangular attention keeps protein geometry consistent", ha="center",va="center",fontsize=14,color=CYAN,alpha=a, path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) ax.text(8,0.5,"Sources: 'how AlphaFold actually works' | NEJM 'What Is AlphaFold' | 'The brilliance of AlphaFold 3'", ha="center",va="center",fontsize=8.5,color=GREY,alpha=a) save_f() # PAIR REPRESENTATION for f in range(SF["pair"]): t=f/SF["pair"]; clear(ax) title_text(ax,"Pair Representation in AlphaFold",y=8.4,fontsize=20) ea=ease(min(1,t*3)) edge(ax,A,B,color=BLUE,lw=2.5,alpha=ea) edge(ax,A,C,color=BLUE,lw=2.5,alpha=ea) edge(ax,B,C,color=BLUE,lw=2.5,alpha=ea) if ea>0.3: node(ax,A,"A"); node(ax,B,"B"); node(ax,C,"C") edge_label(ax,A,B,"d(A,B)",color=YELLOW,offset=(0,0.40)) edge_label(ax,A,C,"d(A,C)",color=ORANGE,offset=(-0.55,-0.1)) edge_label(ax,B,C,"d(B,C)",color=ORANGE,offset=(0.55,-0.1)) body_text(ax,[ "Every amino-acid pair (i, j) stores a learned representation", "encoding distances and angles between residues.", "", '"...the pair representation might encode distances between each', ' amino acid, or the distances and angles relative to each other."', " \u2014 how AlphaFold actually works", ],y_start=7.6,dy=0.48,fontsize=11) save_f() # VIOLATION for f in range(SF["viol"]): t=f/SF["viol"]; clear(ax) title_text(ax,"Problem: Standard Attention Can Violate Geometry",y=8.4,fontsize=17,color=RED) pulse=0.5+0.5*math.sin(t*math.pi*6) if t<0.4: draw_tri(ax,col_ab=WHITE,col_ac=GREY,col_bc=GREY,lw=2.5) edge_label(ax,A,B,"A\u2194B updated in isolation",color=YELLOW,offset=(0,0.42),fontsize=11) else: draw_tri(ax,col_ab=RED,col_ac=GREY,col_bc=GREY,lw=3+pulse) xa=ease(max(0,(t-0.55)/0.25)) ax.text(8,4.1,"\u2717",ha="center",va="center",fontsize=60,color=RED,alpha=xa*0.7,zorder=10) edge_label(ax,A,C,"d(A,C)",color=GREY,offset=(-0.55,-0.1)) edge_label(ax,B,C,"d(B,C)",color=GREY,offset=(0.55,-0.1)) body_text(ax,[ '"Standard attention updates d(A,B) separately from d(A,C) and d(B,C).', " This can produce d(A,B) > d(A,C)+d(C,B) \u2014 impossible in real 3D space!\"", " \u2014 how AlphaFold actually works", ],y_start=1.85,dy=0.50,fontsize=11) save_f() # THEOREM for f in range(SF["thm"]): t=f/SF["thm"]; clear(ax) title_text(ax,"The Triangle Inequality Theorem",y=8.45,fontsize=20,color=YELLOW) draw_tri(ax,col_ab=CYAN,col_ac=GREEN,col_bc=GREEN,lw=3,nc=(CYAN,CYAN,GREEN)) t1=ease(min(1,t/0.3)); t2=ease(max(0,(t-0.3)/0.3)); t3=ease(max(0,(t-0.65)/0.3)) edge_label(ax,A,B,"d(A,B)",color=CYAN,offset=(0,0.40),fontsize=13) edge_label(ax,A,C,"d(A,C)",color=GREEN,offset=(-0.55,-0.1),fontsize=13) edge_label(ax,B,C,"d(C,B)",color=GREEN,offset=(0.55,-0.1),fontsize=13) if t1>0.05: ax.text(8,7.15,"d(A,C) + d(C,B)",ha="center",va="center",fontsize=16, color=GREEN,alpha=t1,fontweight="bold", path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) if t2>0.05: ax.text(8,6.58,"\u2265 d(A,B)",ha="center",va="center",fontsize=16, color=CYAN,alpha=t2,fontweight="bold", path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) if t3>0.05: highlight_box(ax,2.5,6.25,11,1.1,color=YELLOW,alpha=0.13*t3) ax.text(8,6.82,"The sum of any two sides is always \u2265 the third side.", ha="center",va="center",fontsize=13,color=YELLOW,alpha=t3,fontweight="bold", path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) body_text(ax,[ '"...the triangle inequality theorem: the sum of two edges on a triangle', ' must be equal to or greater than the third."', " \u2014 What Is AlphaFold, NEJM", ],y_start=1.65,dy=0.48,fontsize=11,color=GREY) save_f() # TRIANGULAR ATTENTION FIX for f in range(SF["attn"]): t=f/SF["attn"]; clear(ax) title_text(ax,"Fix: Triangular Attention",y=8.45,fontsize=20,color=GREEN) t2=ease(max(0,(t-0.35)/0.35)); t3=ease(max(0,(t-0.70)/0.30)) draw_tri(ax,col_ab=CYAN,col_ac=GREEN,col_bc=GREEN,lw=2.5,nc=(CYAN,CYAN,GREEN)) edge_label(ax,A,B,"d(A,B)",color=CYAN,offset=(0,0.40),fontsize=13) edge_label(ax,A,C,"d(A,C)",color=GREEN,offset=(-0.55,-0.1),fontsize=13) edge_label(ax,B,C,"d(C,B)",color=GREEN,offset=(0.55,-0.1),fontsize=13) if t2>0.05: prog=t2; n=max(2,int(prog*50)) xs=np.linspace(A[0],B[0],n) ys=[(1-s)**2*A[1]+2*(1-s)*s*C[1]+s**2*B[1] for s in np.linspace(0,1,n)] ax.plot(xs,ys,color=ORANGE,lw=3,alpha=0.9*t2,zorder=6,linestyle="--") idx=min(n-1,int(prog*(n-1))) if idx>0: ax.annotate("",xy=(xs[idx],ys[idx]),xytext=(xs[max(idx-1,0)],ys[max(idx-1,0)]), arrowprops=dict(arrowstyle="-|>",color=ORANGE,lw=2,mutation_scale=18),zorder=7) ax.text(8,3.7,"d(A,B) update attends to ALL pairs involving A and B\n(d(A,C) and d(B,C) consulted together)", ha="center",va="center",fontsize=12,color=ORANGE,alpha=t2,fontweight="bold", path_effects=[pe.withStroke(linewidth=3,foreground=BG)]) if t3>0.05: ax.text(8,7.4,"\u2713 Triangular Attention",ha="center",va="center", fontsize=18,color=GREEN,alpha=t3,fontweight="bold", path_effects=[pe.withStroke(linewidth=4,foreground=BG)]) body_text(ax,[ '"Instead of looking at all pairs separately, get the attention mechanism', " to attend to all pairs involving A and B.", ' This is called triangular attention." \u2014 how AlphaFold actually works', ],y_start=1.65,dy=0.48,fontsize=11,color=GREY) save_f() # CONTEXT for f in range(SF["ctx"]): t=f/SF["ctx"]; clear(ax) title_text(ax,"Why This Matters for Protein Structure",y=8.45,fontsize=18,color=PURPLE) n_aa=6; cx0=2.0; cy0=4.8; spacing=2.0 cc=[BLUE,CYAN,GREEN,YELLOW,ORANGE,PURPLE] ts=ease(min(1,t*2.5)) for i in range(n_aa): sa=ease(max(0,ts-i*0.15)) if sa<0.02: continue cx=cx0+i*spacing; cy=cy0+(0.3 if i%2==0 else -0.3); col=cc[i%len(cc)] ta=np.array([cx-0.3,cy+0.35]); tb=np.array([cx+0.3,cy+0.35]); tc=np.array([cx,cy-0.25]) for p1,p2 in [(ta,tb),(tb,tc),(tc,ta)]: ax.plot([p1[0],p2[0]],[p1[1],p2[1]],color=col,lw=2,alpha=sa*0.9) ax.text(cx,cy+0.08,str(i+1),ha="center",va="center",fontsize=9,color=col,alpha=sa,fontweight="bold") if i<n_aa-1: nx=cx0+(i+1)*spacing; ny=cy0+(0.3 if (i+1)%2==0 else -0.3) ax.plot([cx+0.3,nx-0.3],[cy+0.35,ny+0.35],color=GREY,lw=1.5,linestyle="--",alpha=sa*0.6) body_text(ax,[ "Each amino acid acts as a rigid triangle in 3D space.", "Triangular attention ensures the pair representation encodes", "distances that are geometrically consistent \u2014 no impossible gaps.", "", '"...the basic idea is to treat each amino acid as if it\'s a triangle."', " \u2014 how AlphaFold actually works", ],y_start=7.7,dy=0.52,fontsize=11.5) save_f() # OUTRO for f in range(SF["outro"]): t=f/SF["outro"]; clear(ax) ao=1.0-ease(max(0,(t-0.75)/0.25)) draw_tri(ax,col_ab=CYAN,col_ac=GREEN,col_bc=GREEN,lw=2,nc=(CYAN,CYAN,GREEN)) ax.text(8,8.1,"Summary",ha="center",va="center",fontsize=22,color=WHITE, fontweight="bold",alpha=ao,path_effects=[pe.withStroke(linewidth=5,foreground=BG)]) bullets=[ (GREEN,"Pair representation stores distances/angles between all residue pairs"), (RED,"Standard attention can violate the triangle inequality"), (YELLOW,"Triangle inequality: d(A,B) \u2264 d(A,C) + d(C,B)"), (CYAN,"Triangular attention consults all pairs sharing an endpoint"), (PURPLE,"Result: geometrically consistent, physically plausible structures"), ] for i,(col,txt) in enumerate(bullets): tb2=ease(max(0,t*6-i)) ax.text(2.0,6.9-i*0.75,"\u25cf",ha="left",va="center",fontsize=13,color=col,alpha=tb2*ao, path_effects=[pe.withStroke(linewidth=2,foreground=BG)]) ax.text(2.55,6.9-i*0.75,txt,ha="left",va="center",fontsize=11.5,color=WHITE,alpha=tb2*ao, path_effects=[pe.withStroke(linewidth=2,foreground=BG)]) ax.text(8,0.5,"Sources: 'how AlphaFold actually works' | NEJM 'What Is AlphaFold' | 'The brilliance of AlphaFold 3'", ha="center",va="center",fontsize=8.5,color=GREY,alpha=ao) save_f() plt.close(fig) print(f"Saved {fidx} frames.") out="/home/daytona/workspace/alphafold-animation/alphafold_triangular_inequality.mp4" cmd=["ffmpeg","-y","-framerate",str(FPS),"-i",f"{FRAMES_DIR}/frame_%05d.png", "-c:v","libx264","-pix_fmt","yuv420p","-crf","18","-preset","fast",out] r=subprocess.run(cmd,capture_output=True,text=True) if r.returncode==0: sz=os.path.getsize(out) print(f"Encoded: {out} ({sz/1024/1024:.1f} MB)") else: print("FFMPEG ERR:", r.stderr[-800:]) PYEOF echo "Written."

Running Command

cd /home/daytona/workspace/alphafold-animation && python3 animate.py 2>&1

Generate animations frame-by-frame as GIF or PNG sequence

Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.

Generate animations frame-by-frame as GIF or PNG sequence

Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.
This is a shared conversation. Sign in to Orris to start your own chat.