Make alphafold triangular inequality rule animation clip using this references
cd /home/daytona/workspace/attachments/fb0bf012-9624-47a8-a5a3-c37526c10ced && p…
mkdir -p /home/daytona/workspace/alphafold-animation && python3 -c "import matpl…
~/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!")
cd /home/daytona/workspace/alphafold-animation && python3 animate.py 2>&1
ffmpeg -version 2>&1 | head -3 && python3 -c "import matplotlib; print(matplotli…
~/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:])
~/alphafold-animation/animate.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/animate.py'
cat > /home/daytona/workspace/alphafold-animation/animate.py << 'PYEOF' import n…
cd /home/daytona/workspace/alphafold-animation && python3 animate.py 2>&1
Generate animations frame-by-frame as GIF or PNG sequence
Generate animations frame-by-frame as GIF or PNG sequence