make alphafold diffusion model animation clip using this references
cd /home/daytona/workspace/attachments/58e67170-4be3-4179-95e6-0a5015129f4e && p…
cd /home/daytona/workspace/attachments/58e67170-4be3-4179-95e6-0a5015129f4e && p…
mkdir -p /home/daytona/workspace/alphafold-animation && python3 -c "import matpl…
~/alphafold-animation/alphafold_diffusion.py
"""
AlphaFold 3 Diffusion Model Animation
Based on references:
- "how AlphaFold actually works"
- "The brilliance of AlphaFold 3"
- "What Is AlphaFold" (NEJM)
Scenes:
0 Title card
1 Protein folding problem - 1D sequence -> 3D structure
2 AlphaFold 3 inputs (protein / DNA / RNA / ligand)
3 MSA + pair representation encoding
4 Diffusion concept - noise to structure
5 Denoising steps - atoms emerge from cloud
6 Final structure - clean 3D protein backbone
7 Applications - drug discovery, variants, PPIs
8 End 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 FuncAnimation, FFMpegWriter
from matplotlib.patches import FancyArrowPatch, Circle, FancyBboxPatch
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.cm as cm
rng = np.random.default_rng(42)
# ── palette ──────────────────────────────────────────────────────────────────
BG = "#0a0e1a"
BLUE = "#4fc3f7"
CYAN = "#00e5ff"
PURPLE = "#ce93d8"
PINK = "#f48fb1"
GREEN = "#a5d6a7"
YELLOW = "#fff176"
ORANGE = "#ffcc80"
WHITE = "#ffffff"
GREY = "#546e7a"
# ── helpers ───────────────────────────────────────────────────────────────────
def clear(ax):
ax.cla()
ax.set_facecolor(BG)
ax.axis("off")
def title_text(ax, txt, y=0.93, size=20, color=CYAN):
ax.text(0.5, y, txt, transform=ax.transAxes,
ha="center", va="top", fontsize=size, color=color,
fontweight="bold",
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
def subtitle(ax, txt, y=0.06, size=11, color=GREY):
ax.text(0.5, y, txt, transform=ax.transAxes,
ha="center", va="bottom", fontsize=size, color=color,
style="italic")
# ── scene helpers ─────────────────────────────────────────────────────────────
def amino_acid_sequence(ax, t):
"""1-D amino acid string scrolling left."""
aas = list("MKTAYIAKQRQISFVKSHFSRQ")
colors = [BLUE, GREEN, PINK, ORANGE, PURPLE, YELLOW, CYAN]
for i, aa in enumerate(aas):
x = 0.04 + i * 0.044 - t * 0.008
if 0.01 < x < 0.99:
col = colors[i % len(colors)]
ax.text(x, 0.62, aa, transform=ax.transAxes,
ha="center", va="center", fontsize=14,
color=col, fontweight="bold",
path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
# connecting line
if i > 0:
x_prev = 0.04 + (i-1) * 0.044 - t * 0.008
if 0.01 < x_prev < 0.99:
ax.annotate("", xy=(x - 0.018, 0.62),
xytext=(x_prev + 0.018, 0.62),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-", color=GREY, lw=1.2))
def draw_protein_blob(ax, cx, cy, r=0.18, alpha=1.0, color=BLUE):
"""Draw a rough blob representing a folded protein."""
theta = np.linspace(0, 2*np.pi, 80)
np.random.seed(7)
noise = np.random.rand(80)*0.04
radii = r + noise
xs = cx + radii * np.cos(theta)
ys = cy + radii * np.sin(theta)
ax.fill(xs, ys, color=color, alpha=alpha * 0.35)
ax.plot(xs, ys, color=color, lw=1.5, alpha=alpha)
def draw_backbone(ax, pts, color=CYAN, lw=2, alpha=1.0):
xs = [p[0] for p in pts]
ys = [p[1] for p in pts]
ax.plot(xs, ys, color=color, lw=lw, alpha=alpha)
ax.scatter(xs, ys, s=25, color=color, alpha=alpha, zorder=5)
def noisy_backbone(pts, sigma):
"""Add Gaussian noise to backbone points."""
pts = np.array(pts)
return pts + rng.normal(0, sigma, pts.shape)
# ── generate clean backbone path ──────────────────────────────────────────────
def make_clean_backbone():
"""Helical backbone for a 20-residue segment."""
t = np.linspace(0, 4 * np.pi, 20)
xs = 0.5 + 0.25 * np.cos(t)
ys = 0.2 + 0.55 * (t / (4*np.pi))
return list(zip(xs.tolist(), ys.tolist()))
CLEAN_BACKBONE = make_clean_backbone()
# ── SCENE FUNCTIONS (each returns frames_count) ──────────────────────────────
TOTAL_FRAMES = 0 # filled later
def scene_title(ax, f):
"""Scene 0 - Title (30 frames)."""
clear(ax)
alpha = min(1.0, f / 10)
ax.text(0.5, 0.60, "AlphaFold 3", transform=ax.transAxes,
ha="center", va="center", fontsize=34, color=CYAN,
fontweight="bold", alpha=alpha,
path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
ax.text(0.5, 0.44, "Diffusion Model Animation", transform=ax.transAxes,
ha="center", va="center", fontsize=18, color=WHITE,
alpha=alpha*0.9,
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
ax.text(0.5, 0.30, "How atoms emerge from noise", transform=ax.transAxes,
ha="center", va="center", fontsize=13, color=PURPLE,
style="italic", alpha=alpha*0.8)
# bottom credit
ax.text(0.5, 0.10, "Based on: AlphaFold 3 (Abramson et al., 2024) | DeepMind",
transform=ax.transAxes, ha="center", va="center",
fontsize=9, color=GREY, alpha=alpha*0.7)
# decorative dots
for i in range(40):
x = rng.uniform(0.02, 0.98)
y = rng.uniform(0.02, 0.98)
s = rng.uniform(2, 12)
c = rng.choice([BLUE, PURPLE, CYAN, GREEN])
ax.scatter(x, y, s=s, color=c, alpha=alpha * rng.uniform(0.1, 0.4),
transform=ax.transAxes, zorder=1)
def scene_protein_problem(ax, f):
"""Scene 1 - Protein folding problem (40 frames)."""
clear(ax)
alpha = min(1.0, f / 8)
title_text(ax, "The Protein Folding Problem", color=CYAN)
subtitle(ax, "50+ years unsolved — until AlphaFold")
# 1-D sequence
ax.text(0.05, 0.77, "1D Amino Acid Sequence:", transform=ax.transAxes,
fontsize=11, color=WHITE, alpha=alpha)
amino_acid_sequence(ax, f)
# arrow down
if f > 10:
a2 = min(1.0, (f-10)/8)
ax.annotate("", xy=(0.5, 0.43), xytext=(0.5, 0.56),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=YELLOW,
lw=2, mutation_scale=18),
annotation_clip=False)
ax.text(0.54, 0.50, "How does it\nfold?", transform=ax.transAxes,
fontsize=10, color=YELLOW, alpha=a2, va="center")
# 3D blob
if f > 18:
a3 = min(1.0, (f-18)/10)
draw_protein_blob(ax, 0.5, 0.28, r=0.16, alpha=a3)
ax.text(0.5, 0.10, "3D Folded Structure", transform=ax.transAxes,
ha="center", fontsize=11, color=GREEN, alpha=a3)
# methods fade in
if f > 28:
a4 = min(1.0, (f-28)/8)
methods = ["X-ray crystallography", "NMR spectroscopy", "Cryo-EM"]
for i, m in enumerate(methods):
ax.text(0.05 + i*0.32, 0.02, m, transform=ax.transAxes,
fontsize=8, color=GREY, alpha=a4*0.8, ha="left")
def scene_af3_inputs(ax, f):
"""Scene 2 - AF3 handles multiple modalities (35 frames)."""
clear(ax)
alpha = min(1.0, f / 8)
title_text(ax, "AlphaFold 3 — Multiple Modalities", color=PURPLE)
subtitle(ax, "Proteins, DNA, RNA, and Ligands — all in one model")
items = [
(0.18, 0.55, "🧬 Protein", BLUE, "Amino acid chains"),
(0.50, 0.55, "🧪 DNA/RNA", GREEN, "Nucleic acids"),
(0.82, 0.55, "💊 Ligand", ORANGE, "Drug candidates"),
]
for i, (x, y, label, col, sub) in enumerate(items):
delay = i * 8
if f > delay:
a = min(1.0, (f - delay) / 8)
box = FancyBboxPatch((x-0.13, y-0.12), 0.26, 0.24,
boxstyle="round,pad=0.02",
linewidth=2, edgecolor=col,
facecolor=col + "22",
transform=ax.transAxes, zorder=3,
alpha=a)
ax.add_patch(box)
ax.text(x, y+0.04, label, transform=ax.transAxes,
ha="center", fontsize=13, color=col,
fontweight="bold", alpha=a)
ax.text(x, y-0.06, sub, transform=ax.transAxes,
ha="center", fontsize=9, color=WHITE, alpha=a*0.8)
# AF3 box
if f > 25:
a = min(1.0, (f-25)/8)
box = FancyBboxPatch((0.30, 0.12), 0.40, 0.16,
boxstyle="round,pad=0.02",
linewidth=2.5, edgecolor=CYAN,
facecolor=CYAN + "22",
transform=ax.transAxes, zorder=3, alpha=a)
ax.add_patch(box)
ax.text(0.50, 0.20, "AlphaFold 3 — Unified Model",
transform=ax.transAxes, ha="center", fontsize=12,
color=CYAN, fontweight="bold", alpha=a)
# arrows pointing down
for x in [0.18, 0.50, 0.82]:
ax.annotate("", xy=(0.50, 0.28), xytext=(x, 0.43),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=GREY,
lw=1.2, mutation_scale=12),
annotation_clip=False)
def scene_msa_pair(ax, f):
"""Scene 3 - MSA + Pair representation (40 frames)."""
clear(ax)
alpha = min(1.0, f / 8)
title_text(ax, "Input Representations", color=BLUE)
subtitle(ax, "MSA captures evolution | Pair encodes residue relationships")
n = 8 # matrix size
# MSA matrix
if f > 5:
a = min(1.0, (f-5)/8)
ax.text(0.16, 0.83, "MSA (Multiple Sequence Alignment)",
transform=ax.transAxes, ha="center", fontsize=11,
color=GREEN, alpha=a)
for i in range(n):
for j in range(n):
val = rng.random()
col = plt.cm.Greens(val * 0.8 + 0.2)
rect = mpatches.FancyBboxPatch(
(0.02 + j*0.027, 0.48 + i*0.032), 0.024, 0.028,
boxstyle="square,pad=0", linewidth=0.3,
edgecolor=BG, facecolor=col,
transform=ax.transAxes, alpha=a*0.85)
ax.add_patch(rect)
ax.text(0.16, 0.44, "Species / Sequences →",
transform=ax.transAxes, ha="center", fontsize=8,
color=GREY, alpha=a)
# Pair representation matrix
if f > 15:
a = min(1.0, (f-15)/8)
ax.text(0.60, 0.83, "Pair Representation",
transform=ax.transAxes, ha="center", fontsize=11,
color=PURPLE, alpha=a)
for i in range(n):
for j in range(n):
val = np.exp(-((i-j)**2)/8.0) + rng.random()*0.2
col = plt.cm.Purples(min(val * 0.6 + 0.1, 1.0))
rect = mpatches.FancyBboxPatch(
(0.38 + j*0.034, 0.48 + i*0.034), 0.031, 0.031,
boxstyle="square,pad=0", linewidth=0.3,
edgecolor=BG, facecolor=col,
transform=ax.transAxes, alpha=a*0.85)
ax.add_patch(rect)
ax.text(0.60, 0.44, "Residue i vs Residue j (distances / angles)",
transform=ax.transAxes, ha="center", fontsize=8,
color=GREY, alpha=a)
# Triangle inequality note
if f > 26:
a = min(1.0, (f-26)/8)
ax.text(0.50, 0.35, "Triangular Attention",
transform=ax.transAxes, ha="center", fontsize=13,
color=YELLOW, fontweight="bold", alpha=a)
ax.text(0.50, 0.27,
"Respects triangle inequality:\n"
"d(A,B) ≤ d(A,C) + d(C,B)",
transform=ax.transAxes, ha="center", fontsize=10,
color=WHITE, alpha=a*0.9)
# Arrow MSA -> pair
if f > 20:
a = min(1.0, (f-20)/8)
ax.annotate("", xy=(0.37, 0.64), xytext=(0.28, 0.64),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=YELLOW,
lw=2, mutation_scale=14), alpha=a,
annotation_clip=False)
ax.text(0.325, 0.68, "info flows →",
transform=ax.transAxes, ha="center", fontsize=8,
color=YELLOW, alpha=a)
def scene_diffusion_concept(ax, f):
"""Scene 4 - Diffusion concept (45 frames)."""
clear(ax)
alpha = min(1.0, f / 8)
title_text(ax, "The Diffusion Idea", color=ORANGE)
subtitle(ax, "Same magic as AI art (Midjourney, DALL-E) — applied to atoms")
# 5 stages: clean -> noisy
stages = 5
for s in range(stages):
delay = s * 6
if f < delay:
continue
a = min(1.0, (f - delay) / 6)
noise_lvl = s / (stages - 1) # 0 (clean) -> 1 (pure noise)
cx = 0.10 + s * 0.185
cy = 0.55
pts = np.array(CLEAN_BACKBONE[:12])
# scale to local view
pts_x = cx + (pts[:, 0] - 0.5) * 0.16
pts_y = cy + (pts[:, 1] - 0.5) * 0.30
pts_local = np.stack([pts_x, pts_y], axis=1)
if noise_lvl < 0.05:
sigma = 0
else:
sigma = noise_lvl * 0.06
pts_noisy = pts_local + rng.normal(0, sigma, pts_local.shape)
# draw cloud if noisy enough
if noise_lvl > 0.2:
for _ in range(int(30 * noise_lvl)):
nx = rng.normal(cx, 0.05 * noise_lvl)
ny = rng.normal(cy, 0.09 * noise_lvl)
ax.scatter(nx, ny, s=6, color=ORANGE,
alpha=a * noise_lvl * 0.5,
transform=ax.transAxes)
color = plt.cm.RdYlBu_r(noise_lvl)
draw_backbone(ax, pts_noisy.tolist(), color=color,
lw=max(0.5, 2-noise_lvl*1.5), alpha=a)
# label
label = ["Clean", "+Noise", "Noisier", "Very\nNoisy", "Pure\nNoise"][s]
ax.text(cx, cy - 0.22, label, transform=ax.transAxes,
ha="center", fontsize=9, color=WHITE, alpha=a * 0.9)
# arrow left to right
if f > 10:
a = min(1.0, (f-10)/6)
ax.annotate("", xy=(0.93, 0.55), xytext=(0.07, 0.55),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=GREY,
lw=1.5, mutation_scale=16),
annotation_clip=False)
ax.text(0.50, 0.78, "Training: add noise",
transform=ax.transAxes, ha="center", fontsize=11,
color=ORANGE, alpha=a)
if f > 30:
a = min(1.0, (f-30)/8)
ax.text(0.50, 0.20,
"Inference: reverse the process — denoise random cloud → structure",
transform=ax.transAxes, ha="center", fontsize=11,
color=CYAN, alpha=a)
def scene_denoising(ax, f, total=60):
"""Scene 5 - Denoising steps (60 frames)."""
clear(ax)
title_text(ax, "Diffusion Denoising — Atoms Emerge from Noise", color=CYAN)
subtitle(ax, "AlphaFold 3 predicts atom positions step by step")
progress = f / total # 0 -> 1
n_atoms = 30
rng2 = np.random.default_rng(99)
# Final positions (clean structure helix)
t = np.linspace(0, 3 * np.pi, n_atoms)
final_x = 0.50 + 0.22 * np.cos(t)
final_y = 0.18 + 0.60 * (t / (3*np.pi))
# Random start positions
start_x = rng2.uniform(0.1, 0.9, n_atoms)
start_y = rng2.uniform(0.1, 0.9, n_atoms)
# Interpolate
ease = progress ** 1.5 # ease-in
cur_x = start_x + ease * (final_x - start_x)
cur_y = start_y + ease * (final_y - start_y)
# Residual noise decreasing
noise_sigma = (1 - ease) * 0.06
cur_x += rng2.normal(0, noise_sigma, n_atoms)
cur_y += rng2.normal(0, noise_sigma, n_atoms)
# Background noise cloud fading out
n_cloud = int(200 * (1 - ease))
if n_cloud > 0:
cx = rng2.uniform(0.05, 0.95, n_cloud)
cy = rng2.uniform(0.05, 0.95, n_cloud)
ax.scatter(cx, cy, s=4, color=ORANGE,
alpha=(1-ease)*0.3, transform=ax.transAxes, zorder=1)
# Atom colors by element type
colors = [BLUE if i % 3 == 0 else GREEN if i % 3 == 1 else PINK
for i in range(n_atoms)]
sizes = [60 if i % 5 == 0 else 30 for i in range(n_atoms)]
ax.scatter(cur_x, cur_y, s=sizes, c=colors, alpha=0.85,
transform=ax.transAxes, zorder=4)
# Draw bonds between close atoms (final stage)
if ease > 0.4:
bond_alpha = min(1.0, (ease - 0.4) / 0.4)
for i in range(n_atoms - 1):
ax.plot([cur_x[i], cur_x[i+1]],
[cur_y[i], cur_y[i+1]],
color=CYAN, lw=1.0,
alpha=bond_alpha * 0.6,
transform=ax.transAxes, zorder=3)
# Step counter
step_shown = int(progress * 200)
ax.text(0.85, 0.92, f"Step {step_shown}/200",
transform=ax.transAxes, ha="center", fontsize=11,
color=YELLOW, fontweight="bold")
# Progress bar
bar_w = 0.70 * progress
ax.add_patch(mpatches.FancyBboxPatch(
(0.15, 0.05), bar_w, 0.025,
boxstyle="round,pad=0.002",
facecolor=CYAN, edgecolor="none",
transform=ax.transAxes, alpha=0.7))
ax.add_patch(mpatches.FancyBboxPatch(
(0.15, 0.05), 0.70, 0.025,
boxstyle="round,pad=0.002",
facecolor="none", edgecolor=GREY, lw=1,
transform=ax.transAxes))
ax.text(0.50, 0.035, "Denoising Progress",
transform=ax.transAxes, ha="center", fontsize=9, color=GREY)
# Noise level label
noise_pct = int((1 - ease) * 100)
ax.text(0.15, 0.92, f"Noise: {noise_pct}%",
transform=ax.transAxes, ha="left", fontsize=11,
color=ORANGE)
def scene_final_structure(ax, f):
"""Scene 6 - Final structure revealed (40 frames)."""
clear(ax)
alpha = min(1.0, f / 10)
title_text(ax, "Predicted 3D Atomic Structure", color=GREEN)
subtitle(ax, "Final coordinates for every atom in the complex")
# Draw helix backbone
n = 40
t = np.linspace(0, 6 * np.pi, n)
xs = 0.50 + 0.20 * np.cos(t)
ys = 0.15 + 0.70 * (t / (6*np.pi))
# Fade in ribbon
n_show = max(2, int(n * min(1.0, f / 20)))
for i in range(n_show - 1):
frac = i / n
col = plt.cm.cool(frac)
ax.plot([xs[i], xs[i+1]], [ys[i], ys[i+1]],
color=col, lw=4, alpha=alpha,
transform=ax.transAxes, solid_capstyle='round')
# Atoms
if f > 8:
a_atoms = min(1.0, (f-8)/8)
cols = [BLUE if i % 3 == 0 else GREEN if i % 3 == 1 else PINK
for i in range(n_show)]
ax.scatter(xs[:n_show], ys[:n_show], s=35,
c=cols[:n_show], alpha=a_atoms * 0.9,
transform=ax.transAxes, zorder=5)
# Side-chain stubs
if f > 16:
a_side = min(1.0, (f-16)/8)
for i in range(0, n_show, 3):
dx = 0.03 * np.cos(t[i] + np.pi/3)
dy = 0.03 * np.sin(t[i] + np.pi/3)
ax.plot([xs[i], xs[i]+dx], [ys[i], ys[i]+dy],
color=ORANGE, lw=1.5, alpha=a_side * 0.8,
transform=ax.transAxes)
ax.scatter(xs[i]+dx, ys[i]+dy, s=15,
color=ORANGE, alpha=a_side * 0.9,
transform=ax.transAxes, zorder=6)
# Labels
if f > 25:
a_lbl = min(1.0, (f-25)/8)
ax.text(0.78, 0.75, "α-helix", transform=ax.transAxes,
fontsize=11, color=CYAN, alpha=a_lbl,
path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
ax.annotate("", xy=(0.72, 0.65), xytext=(0.77, 0.73),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=CYAN,
lw=1.2, mutation_scale=10),
annotation_clip=False)
ax.text(0.78, 0.45, "Side chains", transform=ax.transAxes,
fontsize=10, color=ORANGE, alpha=a_lbl)
# Accuracy note
if f > 32:
a_acc = min(1.0, (f-32)/6)
ax.text(0.50, 0.06,
"Accuracy comparable to X-ray crystallography",
transform=ax.transAxes, ha="center", fontsize=11,
color=YELLOW, alpha=a_acc,
path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
def scene_applications(ax, f):
"""Scene 7 - Applications (40 frames)."""
clear(ax)
alpha = min(1.0, f / 8)
title_text(ax, "Applications", color=YELLOW)
subtitle(ax, "From structure to impact")
apps = [
(0.22, 0.65, "💊 Drug\nDiscovery", ORANGE, 0),
(0.50, 0.65, "🧬 Genetic\nVariants", PINK, 10),
(0.78, 0.65, "🤝 Protein-Protein\nInteractions", BLUE, 20),
(0.22, 0.30, "🌱 Protein\nEngineering", GREEN, 28),
(0.50, 0.30, "🔬 Ligand\nBinding", PURPLE, 34),
(0.78, 0.30, "🧫 Biomedicine\n& Agriculture", CYAN, 40),
]
for (x, y, label, col, delay) in apps:
if f > delay:
a = min(1.0, (f - delay) / 8)
r = 0.11
circ = Circle((x, y), r, transform=ax.transAxes,
facecolor=col + "25", edgecolor=col,
linewidth=2, alpha=a, zorder=3)
ax.add_patch(circ)
ax.text(x, y, label, transform=ax.transAxes,
ha="center", va="center", fontsize=9.5,
color=col, fontweight="bold", alpha=a)
def scene_end(ax, f):
"""Scene 8 - End card (25 frames)."""
clear(ax)
alpha = min(1.0, f / 10)
ax.text(0.5, 0.65, "AlphaFold 3", transform=ax.transAxes,
ha="center", va="center", fontsize=30, color=CYAN,
fontweight="bold", alpha=alpha,
path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
ax.text(0.5, 0.50, "From noisy atoms to protein structures\nvia diffusion",
transform=ax.transAxes, ha="center", va="center",
fontsize=14, color=WHITE, alpha=alpha * 0.9)
ax.text(0.5, 0.33,
"Abramson et al., Nature 2024\n"
"\"Accurate structure prediction of biomolecular interactions\n"
"with AlphaFold 3\"",
transform=ax.transAxes, ha="center", va="center",
fontsize=10, color=GREY, alpha=alpha * 0.8, style="italic")
ax.text(0.5, 0.10, "Animation generated from reference PDFs",
transform=ax.transAxes, ha="center", fontsize=9,
color=GREY, alpha=alpha * 0.6)
# animated dots orbit
for i in range(20):
angle = 2 * np.pi * i / 20 + f * 0.15
r = 0.40
x = 0.5 + r * np.cos(angle) * 0.4
y = 0.5 + r * np.sin(angle) * 0.22
c = [BLUE, PURPLE, CYAN, GREEN, PINK][i % 5]
ax.scatter(x, y, s=10, color=c, alpha=alpha * 0.5,
transform=ax.transAxes)
# ── scene schedule ────────────────────────────────────────────────────────────
SCENES = [
(scene_title, 30),
(scene_protein_problem, 40),
(scene_af3_inputs, 35),
(scene_msa_pair, 40),
(scene_diffusion_concept, 45),
(scene_denoising, 60),
(scene_final_structure, 40),
(scene_applications, 50),
(scene_end, 30),
]
TOTAL_FRAMES = sum(n for _, n in SCENES)
# Build cumulative frame boundaries
boundaries = []
start = 0
for fn, n in SCENES:
boundaries.append((start, start + n, fn, n))
start += n
def animate(global_f):
for (s, e, fn, n) in boundaries:
if s <= global_f < e:
local_f = global_f - s
if fn is scene_denoising:
fn(ax, local_f, total=n)
else:
fn(ax, local_f)
return
# ── build figure & run ────────────────────────────────────────────────────────
fig, ax = plt.subplots(figsize=(12, 7), facecolor=BG)
ax.set_facecolor(BG)
ax.axis("off")
print(f"Total frames: {TOTAL_FRAMES}")
print("Rendering animation...")
anim = FuncAnimation(fig, animate, frames=TOTAL_FRAMES,
interval=1000/30, blit=False)
writer = FFMpegWriter(fps=30, bitrate=2000,
extra_args=["-vcodec", "libx264",
"-pix_fmt", "yuv420p"])
out_path = "/home/daytona/workspace/alphafold-animation/alphafold3_diffusion.mp4"
anim.save(out_path, writer=writer, dpi=120)
plt.close(fig)
print(f"Saved: {out_path}")
cd /home/daytona/workspace/alphafold-animation && python3 alphafold_diffusion.py…
~/alphafold-animation/alphafold_diffusion.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/alphafold_diffusion.py'
~/alphafold-animation/alphafold_diffusion.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/alphafold_diffusion.py'
~/alphafold-animation/alphafold_diffusion.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/alphafold_diffusion.py'
cd /home/daytona/workspace/alphafold-animation && python3 alphafold_diffusion.py…
ffmpeg -version 2>&1 | head -3 && ffmpeg -f lavfi -i testsrc=duration=1:size=640…
~/alphafold-animation/alphafold_diffusion.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/alphafold-animation/alphafold_diffusion.py'
cd /home/daytona/workspace/alphafold-animation && python3 alphafold_diffusion.py…
give image atleast
AlphaFold 3 diffusion model scientific diagram, dark navy background, showing the complete pipeline: left side shows a 1D amino acid sequence (colored letters M-K-T-A-Y-I connected by lines), center shows the diffusion process with 5 stages from a clean protein 3D helical backbone gradually dissolving into a noisy cloud of orange scattered atoms, then reversing back (denoising steps numbered 1 to 200), right side shows the final predicted 3D protein structure as a colored ribbon diagram (blue to green gradient helix). Middle section has two matrix grids labeled "MSA Multiple Sequence Alignment" (green heatmap) and "Pair Representation" (purple heatmap) with a "Triangular Attention" label and triangle geometry. Bottom row shows 6 application circles: Drug Discovery, Genetic Variants, Protein-Protein Interactions, Protein Engineering, Ligand Binding, Biomedicine. Top title reads "AlphaFold 3 — Diffusion Model" in cyan. Labels and arrows throughout, clean scientific infographic style, glowing atom particles, professional visualization

Generate animations frame-by-frame as GIF or PNG sequence
~/alphafold-animation/make_gif.py
"""
AlphaFold 3 Diffusion Model — Animated GIF
9 scenes, 90 frames total @ 12 fps (~7.5 sec loop)
"""
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 Circle, FancyBboxPatch
from PIL import Image
import os, io
# ── palette ──────────────────────────────────────────────────────────────────
BG = "#0a0e1a"
BLUE = "#4fc3f7"
CYAN = "#00e5ff"
PURPLE = "#b39ddb"
PINK = "#f48fb1"
GREEN = "#81c784"
YELLOW = "#fff176"
ORANGE = "#ffcc80"
WHITE = "#ffffff"
GREY = "#546e7a"
RED = "#ef9a9a"
RNG = np.random.default_rng(42)
# ── figure setup ─────────────────────────────────────────────────────────────
W, H = 900, 540 # pixels (divisible by 2 for ffmpeg if needed)
DPI = 100
FIG_W = W / DPI
FIG_H = H / DPI
def new_fig():
fig, ax = plt.subplots(figsize=(FIG_W, FIG_H), dpi=DPI)
fig.patch.set_facecolor(BG)
ax.set_facecolor(BG)
ax.set_xlim(0, 1); ax.set_ylim(0, 1)
ax.axis("off")
return fig, ax
def fig_to_pil(fig):
buf = io.BytesIO()
fig.savefig(buf, format="png", facecolor=BG, bbox_inches="tight",
pad_inches=0)
buf.seek(0)
img = Image.open(buf).convert("RGB").resize((W, H), Image.LANCZOS)
plt.close(fig)
return img
# ── drawing helpers ───────────────────────────────────────────────────────────
def ttl(ax, txt, y=0.93, size=18, color=CYAN):
ax.text(0.5, y, txt, transform=ax.transAxes,
ha="center", va="top", fontsize=size, color=color,
fontweight="bold",
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
def sub(ax, txt, y=0.05, size=9, color=GREY):
ax.text(0.5, y, txt, transform=ax.transAxes,
ha="center", va="bottom", fontsize=size, color=color,
style="italic")
def ease(t, total):
"""Normalised 0->1 with ease-in ramp over first 8 frames."""
return min(1.0, t / min(8, total))
def backbone_pts(n=18):
"""Helical backbone, normalised 0-1."""
t = np.linspace(0, 4*np.pi, n)
xs = 0.5 + 0.22 * np.cos(t)
ys = 0.15 + 0.68 * (t / (4*np.pi))
return xs, ys
BX, BY = backbone_pts()
# ══════════════════════════════════════════════════════════════════════════════
# SCENE FUNCTIONS — each takes (frame_local, frame_total) and returns PIL img
# ══════════════════════════════════════════════════════════════════════════════
def scene_title(f, n):
fig, ax = new_fig()
a = ease(f, n)
# starfield
rng = np.random.default_rng(1)
xs = rng.uniform(0, 1, 120); ys = rng.uniform(0, 1, 120)
sizes = rng.uniform(1, 8, 120)
cols = rng.choice([BLUE, PURPLE, CYAN, GREEN, WHITE], 120)
for x, y, s, c in zip(xs, ys, sizes, cols):
ax.scatter(x, y, s=s*a, color=c, alpha=a*0.4, zorder=1)
# title
ax.text(0.5, 0.62, "AlphaFold 3",
transform=ax.transAxes, ha="center", fontsize=36,
color=CYAN, fontweight="bold", alpha=a,
path_effects=[pe.withStroke(linewidth=6, foreground=BG)])
ax.text(0.5, 0.47, "Diffusion Model",
transform=ax.transAxes, ha="center", fontsize=20,
color=WHITE, alpha=a*0.9,
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
ax.text(0.5, 0.35, "How atoms emerge from noise",
transform=ax.transAxes, ha="center", fontsize=13,
color=PURPLE, style="italic", alpha=a*0.8)
ax.text(0.5, 0.10,
"Based on: Abramson et al. 2024 (AlphaFold 3) | DeepMind",
transform=ax.transAxes, ha="center", fontsize=8,
color=GREY, alpha=a*0.7)
return fig_to_pil(fig)
def scene_problem(f, n):
fig, ax = new_fig()
a = ease(f, n)
ttl(ax, "The Protein Folding Problem", color=CYAN)
sub(ax, "50+ years unsolved — until AlphaFold")
# 1-D sequence strip
ax.text(0.03, 0.79, "1D Amino Acid Sequence:", fontsize=10,
color=WHITE, alpha=a, transform=ax.transAxes)
aas = list("MKTAYIAKQRQISFVKSHS")
clrs = [BLUE, GREEN, PINK, ORANGE, PURPLE, YELLOW, CYAN, RED]
for i, aa in enumerate(aas):
x = 0.05 + i * 0.048
c = clrs[i % len(clrs)]
ax.text(x, 0.70, aa, transform=ax.transAxes,
ha="center", fontsize=12, color=c,
fontweight="bold", alpha=a,
path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
if i > 0:
ax.annotate("", xy=(x-0.018, 0.70),
xytext=(x-0.030, 0.70),
xycoords="axes fraction",
textcoords="axes fraction",
arrowprops=dict(arrowstyle="-", color=GREY, lw=1))
# arrow down
a2 = min(1.0, max(0, (f-4)/6))
if a2 > 0:
ax.annotate("", xy=(0.5, 0.44), xytext=(0.5, 0.58),
xycoords="axes fraction", textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=YELLOW,
lw=2.5, mutation_scale=18))
ax.text(0.56, 0.52, "How does\nit fold?",
transform=ax.transAxes, fontsize=9,
color=YELLOW, alpha=a2)
# blob
a3 = min(1.0, max(0, (f-8)/6))
if a3 > 0:
t = np.linspace(0, 2*np.pi, 80)
rn = np.random.default_rng(7)
r = 0.17 + rn.random(80)*0.04
xs = 0.5 + r*np.cos(t)
ys = 0.27 + r*np.sin(t)*0.65
ax.fill(xs, ys, color=BLUE, alpha=a3*0.25, transform=ax.transAxes)
ax.plot(xs, ys, color=BLUE, lw=2, alpha=a3, transform=ax.transAxes)
ax.text(0.5, 0.08, "3D Folded Structure",
transform=ax.transAxes, ha="center",
fontsize=11, color=GREEN, alpha=a3)
return fig_to_pil(fig)
def scene_inputs(f, n):
fig, ax = new_fig()
a = ease(f, n)
ttl(ax, "AlphaFold 3 — Multiple Modalities", color=PURPLE)
sub(ax, "One model handles proteins, DNA, RNA and drug-like ligands")
items = [
(0.18, 0.58, "PROTEIN", "Amino acid chains", BLUE),
(0.50, 0.58, "DNA / RNA","Nucleic acids", GREEN),
(0.82, 0.58, "LIGAND", "Drug candidates", ORANGE),
]
for i, (x, y, lbl, sub_, col) in enumerate(items):
delay = i * 4
aa = min(1.0, max(0, (f-delay)/6))
if aa <= 0: continue
box = FancyBboxPatch((x-0.14, y-0.13), 0.28, 0.26,
boxstyle="round,pad=0.02",
linewidth=2, edgecolor=col,
facecolor=col+"22",
transform=ax.transAxes, alpha=aa)
ax.add_patch(box)
ax.text(x, y+0.04, lbl, transform=ax.transAxes,
ha="center", fontsize=13, color=col,
fontweight="bold", alpha=aa)
ax.text(x, y-0.07, sub_, transform=ax.transAxes,
ha="center", fontsize=9, color=WHITE, alpha=aa*0.85)
# AF3 unification box
a_box = min(1.0, max(0, (f-12)/6))
if a_box > 0:
box2 = FancyBboxPatch((0.28, 0.12), 0.44, 0.17,
boxstyle="round,pad=0.02",
linewidth=2.5, edgecolor=CYAN,
facecolor=CYAN+"18",
transform=ax.transAxes, alpha=a_box)
ax.add_patch(box2)
ax.text(0.50, 0.205, "AlphaFold 3 — Unified Embedding Space",
transform=ax.transAxes, ha="center", fontsize=11,
color=CYAN, fontweight="bold", alpha=a_box)
for xi in [0.18, 0.50, 0.82]:
ax.annotate("", xy=(0.50, 0.29), xytext=(xi, 0.45),
xycoords="axes fraction",
textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=GREY,
lw=1.2, mutation_scale=11))
return fig_to_pil(fig)
def scene_msa(f, n):
fig, ax = new_fig()
a = ease(f, n)
ttl(ax, "Input Representations", color=BLUE)
sub(ax, "MSA captures evolution | Pair Representation encodes geometry")
sz = 9
rng2 = np.random.default_rng(3)
# MSA matrix (left)
a1 = min(1.0, max(0, (f-2)/7))
if a1 > 0:
ax.text(0.18, 0.83, "MSA", transform=ax.transAxes,
ha="center", fontsize=12, color=GREEN,
fontweight="bold", alpha=a1)
for i in range(sz):
for j in range(sz):
v = rng2.random()
col = plt.cm.Greens(v*0.75+0.2)
rect = mpatches.Rectangle(
(0.03 + j*0.029, 0.47 + i*0.033), 0.026, 0.030,
linewidth=0.4, edgecolor=BG, facecolor=col,
transform=ax.transAxes, alpha=a1*0.9)
ax.add_patch(rect)
ax.text(0.18, 0.44, "Species / Sequences",
transform=ax.transAxes, ha="center",
fontsize=8, color=GREY, alpha=a1)
# arrow
a2 = min(1.0, max(0, (f-8)/6))
if a2 > 0:
ax.annotate("", xy=(0.40, 0.65), xytext=(0.30, 0.65),
xycoords="axes fraction",
textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=YELLOW,
lw=2, mutation_scale=14))
ax.text(0.35, 0.69, "info\nflows",
transform=ax.transAxes, ha="center",
fontsize=8, color=YELLOW, alpha=a2)
# Pair matrix (right)
a3 = min(1.0, max(0, (f-5)/7))
if a3 > 0:
ax.text(0.68, 0.83, "Pair Representation",
transform=ax.transAxes, ha="center",
fontsize=12, color=PURPLE, fontweight="bold", alpha=a3)
for i in range(sz):
for j in range(sz):
v = np.exp(-((i-j)**2)/6.0) + rng2.random()*0.18
col = plt.cm.Purples(min(v*0.55+0.1, 1.0))
rect = mpatches.Rectangle(
(0.42 + j*0.034, 0.47 + i*0.034), 0.031, 0.031,
linewidth=0.4, edgecolor=BG, facecolor=col,
transform=ax.transAxes, alpha=a3*0.9)
ax.add_patch(rect)
ax.text(0.68, 0.44, "Residue i vs Residue j",
transform=ax.transAxes, ha="center",
fontsize=8, color=GREY, alpha=a3)
# triangle inequality
a4 = min(1.0, max(0, (f-12)/6))
if a4 > 0:
ax.text(0.50, 0.32, "Triangular Attention",
transform=ax.transAxes, ha="center",
fontsize=14, color=YELLOW, fontweight="bold", alpha=a4)
ax.text(0.50, 0.20,
"Enforces: d(A,B) ≤ d(A,C) + d(C,B)",
transform=ax.transAxes, ha="center",
fontsize=10, color=WHITE, alpha=a4*0.9)
# small triangle
tx = [0.34, 0.50, 0.66, 0.34]
ty = [0.10, 0.17, 0.10, 0.10]
ax.plot(tx, ty, color=YELLOW, lw=1.5, alpha=a4*0.7,
transform=ax.transAxes)
for lbl, (lx, ly) in zip("ABC", [(0.34,0.09),(0.50,0.18),(0.66,0.09)]):
ax.text(lx, ly, lbl, transform=ax.transAxes,
ha="center", fontsize=9, color=YELLOW, alpha=a4)
return fig_to_pil(fig)
def scene_diffusion_concept(f, n):
fig, ax = new_fig()
ttl(ax, "The Diffusion Idea", color=ORANGE)
sub(ax, "Same magic as Midjourney / DALL-E — applied to atomic coordinates")
stages = 5
rng3 = np.random.default_rng(5)
labels = ["Clean\nStructure", "+Noise", "Noisier", "Very\nNoisy", "Pure\nNoise"]
for s in range(stages):
delay = s * 4
aa = min(1.0, max(0, (f-delay)/5))
if aa <= 0: continue
noise_lvl = s / (stages-1)
cx = 0.10 + s * 0.195
cy = 0.55
# scaled backbone
bx = cx + (BX - 0.5)*0.14
by = cy + (BY - 0.5)*0.28
sigma = noise_lvl * 0.05
nx = bx + rng3.normal(0, sigma, len(bx))
ny = by + rng3.normal(0, sigma, len(by))
# noise cloud
if noise_lvl > 0.15:
n_cloud = int(35 * noise_lvl)
cx2 = rng3.normal(cx, 0.045*noise_lvl, n_cloud)
cy2 = rng3.normal(cy, 0.08*noise_lvl, n_cloud)
ax.scatter(cx2, cy2, s=5, color=ORANGE,
alpha=aa*noise_lvl*0.55,
transform=ax.transAxes)
col = plt.cm.RdYlBu_r(noise_lvl)
ax.plot(nx, ny, color=col, lw=max(0.5, 2-noise_lvl*1.5),
alpha=aa*0.9, transform=ax.transAxes)
ax.scatter(nx, ny, s=12, color=col, alpha=aa*0.85,
transform=ax.transAxes, zorder=5)
ax.text(cx, cy-0.23, labels[s], transform=ax.transAxes,
ha="center", fontsize=8, color=WHITE, alpha=aa*0.9)
# arrow
a_arr = min(1.0, max(0, (f-8)/5))
if a_arr > 0:
ax.annotate("", xy=(0.92, 0.55), xytext=(0.08, 0.55),
xycoords="axes fraction",
textcoords="axes fraction",
arrowprops=dict(arrowstyle="-|>", color=GREY,
lw=1.5, mutation_scale=15))
ax.text(0.50, 0.79, "TRAINING: add noise",
transform=ax.transAxes, ha="center",
fontsize=12, color=ORANGE, alpha=a_arr, fontweight="bold")
a_inf = min(1.0, max(0, (f-14)/5))
if a_inf > 0:
ax.text(0.50, 0.18,
"INFERENCE: reverse — denoise random cloud -> 3D structure",
transform=ax.transAxes, ha="center",
fontsize=10, color=CYAN, alpha=a_inf)
return fig_to_pil(fig)
def scene_denoising(f, n):
fig, ax = new_fig()
ttl(ax, "Denoising — Atoms Emerge from Noise", color=CYAN)
sub(ax, "AlphaFold 3 predicts atomic coordinates step by step")
progress = f / n # 0 -> 1
ease_p = progress**1.4
n_atoms = 32
rng4 = np.random.default_rng(77)
t = np.linspace(0, 3*np.pi, n_atoms)
fx = 0.50 + 0.23*np.cos(t)
fy = 0.18 + 0.60*(t/(3*np.pi))
sx = rng4.uniform(0.08, 0.92, n_atoms)
sy = rng4.uniform(0.12, 0.88, n_atoms)
cx_ = sx + ease_p*(fx - sx) + rng4.normal(0, (1-ease_p)*0.05, n_atoms)
cy_ = sy + ease_p*(fy - sy) + rng4.normal(0, (1-ease_p)*0.05, n_atoms)
# fading cloud
n_cloud = int(180*(1-ease_p))
if n_cloud > 0:
cxc = rng4.uniform(0.05, 0.95, n_cloud)
cyc = rng4.uniform(0.08, 0.92, n_cloud)
ax.scatter(cxc, cyc, s=4, color=ORANGE,
alpha=(1-ease_p)*0.3,
transform=ax.transAxes, zorder=1)
atom_cols = [BLUE if i%3==0 else GREEN if i%3==1 else PINK
for i in range(n_atoms)]
atom_sz = [55 if i%5==0 else 28 for i in range(n_atoms)]
ax.scatter(cx_, cy_, s=atom_sz, c=atom_cols, alpha=0.88,
transform=ax.transAxes, zorder=4)
# bonds (fade in after 40%)
if ease_p > 0.35:
ba = min(1.0, (ease_p-0.35)/0.4)
for i in range(n_atoms-1):
ax.plot([cx_[i], cx_[i+1]], [cy_[i], cy_[i+1]],
color=CYAN, lw=0.9, alpha=ba*0.65,
transform=ax.transAxes, zorder=3)
# step counter
step = int(progress*200)
ax.text(0.84, 0.91, f"Step {step}/200",
transform=ax.transAxes, ha="center",
fontsize=12, color=YELLOW, fontweight="bold")
noise_pct = int((1-ease_p)*100)
ax.text(0.16, 0.91, f"Noise: {noise_pct}%",
transform=ax.transAxes, ha="left",
fontsize=11, color=ORANGE)
# progress bar
bw = 0.68 * progress
ax.add_patch(FancyBboxPatch((0.16, 0.055), bw, 0.028,
boxstyle="round,pad=0.002",
facecolor=CYAN, edgecolor="none",
transform=ax.transAxes, alpha=0.72))
ax.add_patch(FancyBboxPatch((0.16, 0.055), 0.68, 0.028,
boxstyle="round,pad=0.002",
facecolor="none", edgecolor=GREY, lw=1,
transform=ax.transAxes))
ax.text(0.50, 0.038, "Denoising Progress",
transform=ax.transAxes, ha="center", fontsize=8, color=GREY)
return fig_to_pil(fig)
def scene_structure(f, n):
fig, ax = new_fig()
a = ease(f, n)
ttl(ax, "Predicted 3D Atomic Structure", color=GREEN)
sub(ax, "Final coordinates — accuracy comparable to X-ray crystallography")
n_pts = 40
t = np.linspace(0, 6*np.pi, n_pts)
xs = 0.50 + 0.21*np.cos(t)
ys = 0.14 + 0.70*(t/(6*np.pi))
n_show = max(2, int(n_pts * min(1.0, f/14)))
for i in range(n_show-1):
frac = i/n_pts
col = plt.cm.cool(frac)
ax.plot([xs[i], xs[i+1]], [ys[i], ys[i+1]],
color=col, lw=4.5, alpha=a,
transform=ax.transAxes, solid_capstyle="round")
# atoms
a_at = min(1.0, max(0, (f-6)/8))
if a_at > 0:
atcols = [BLUE if i%3==0 else GREEN if i%3==1 else PINK
for i in range(n_show)]
ax.scatter(xs[:n_show], ys[:n_show], s=30,
c=atcols, alpha=a_at*0.9,
transform=ax.transAxes, zorder=5)
# side chains
a_sc = min(1.0, max(0, (f-12)/8))
if a_sc > 0:
for i in range(0, n_show, 3):
dx = 0.032*np.cos(t[i]+np.pi/3)
dy = 0.032*np.sin(t[i]+np.pi/3)
ax.plot([xs[i], xs[i]+dx], [ys[i], ys[i]+dy],
color=ORANGE, lw=1.5,
alpha=a_sc*0.8, transform=ax.transAxes)
ax.scatter(xs[i]+dx, ys[i]+dy, s=14,
color=ORANGE, alpha=a_sc*0.9,
transform=ax.transAxes, zorder=6)
# labels
a_lb = min(1.0, max(0, (f-16)/6))
if a_lb > 0:
ax.text(0.79, 0.75, "alpha-helix",
transform=ax.transAxes, fontsize=11, color=CYAN,
alpha=a_lb,
path_effects=[pe.withStroke(linewidth=2, foreground=BG)])
ax.text(0.79, 0.55, "Side chains",
transform=ax.transAxes, fontsize=10, color=ORANGE,
alpha=a_lb)
ax.text(0.79, 0.38, "Backbone",
transform=ax.transAxes, fontsize=10, color=BLUE,
alpha=a_lb)
return fig_to_pil(fig)
def scene_apps(f, n):
fig, ax = new_fig()
a = ease(f, n)
ttl(ax, "Applications", color=YELLOW)
sub(ax, "From predicted structure to biological and clinical impact")
apps = [
(0.20, 0.65, "Drug\nDiscovery", ORANGE, 0),
(0.50, 0.65, "Genetic\nVariants", PINK, 5),
(0.80, 0.65, "Protein-Protein\nInteractions", BLUE, 10),
(0.20, 0.30, "Protein\nEngineering", GREEN, 15),
(0.50, 0.30, "Ligand\nBinding", PURPLE, 20),
(0.80, 0.30, "Biomedicine\n& Agriculture", CYAN, 25),
]
for (x, y, lbl, col, delay) in apps:
aa = min(1.0, max(0, (f-delay)/7))
if aa <= 0: continue
circ = Circle((x, y), 0.12, transform=ax.transAxes,
facecolor=col+"22", edgecolor=col,
linewidth=2, alpha=aa)
ax.add_patch(circ)
ax.text(x, y, lbl, transform=ax.transAxes,
ha="center", va="center", fontsize=9.5,
color=col, fontweight="bold", alpha=aa)
return fig_to_pil(fig)
def scene_end(f, n):
fig, ax = new_fig()
a = ease(f, n)
# orbiting dots
for i in range(24):
angle = 2*np.pi*i/24 + f*0.18
rx, ry = 0.40, 0.22
x = 0.5 + rx*np.cos(angle)
y = 0.5 + ry*np.sin(angle)
c = [BLUE, PURPLE, CYAN, GREEN, PINK][i%5]
ax.scatter(x, y, s=12, color=c, alpha=a*0.55,
transform=ax.transAxes)
ax.text(0.5, 0.65, "AlphaFold 3",
transform=ax.transAxes, ha="center", fontsize=32,
color=CYAN, fontweight="bold", alpha=a,
path_effects=[pe.withStroke(linewidth=5, foreground=BG)])
ax.text(0.5, 0.51,
"From noisy atoms to biomolecular structures\nvia diffusion",
transform=ax.transAxes, ha="center", fontsize=14,
color=WHITE, alpha=a*0.9)
ax.text(0.5, 0.34,
"Abramson et al., Nature 2024\n"
"\"Accurate structure prediction of biomolecular\n"
" interactions with AlphaFold 3\"",
transform=ax.transAxes, ha="center", fontsize=9.5,
color=GREY, alpha=a*0.8, style="italic")
ax.text(0.5, 0.10,
"Protein | DNA | RNA | Ligand — one unified model",
transform=ax.transAxes, ha="center", fontsize=10,
color=PURPLE, alpha=a*0.85)
return fig_to_pil(fig)
# ══════════════════════════════════════════════════════════════════════════════
# SCENE SCHEDULE
# ══════════════════════════════════════════════════════════════════════════════
SCHEDULE = [
("Title", scene_title, 10),
("Protein Problem", scene_problem, 14),
("AF3 Inputs", scene_inputs, 16),
("MSA + Pair", scene_msa, 18),
("Diffusion Concept", scene_diffusion_concept, 18),
("Denoising", scene_denoising, 24),
("3D Structure", scene_structure, 18),
("Applications", scene_apps, 18),
("End Card", scene_end, 16),
]
total_frames = sum(nf for _, _, nf in SCHEDULE)
print(f"Total frames: {total_frames}")
# ── render all frames ─────────────────────────────────────────────────────────
frames_dir = "/home/daytona/workspace/alphafold-animation/frames"
os.makedirs(frames_dir, exist_ok=True)
all_pil = []
gf = 0
for (name, fn, nf) in SCHEDULE:
print(f" Rendering: {name} ({nf} frames)...")
for lf in range(nf):
img = fn(lf, nf)
all_pil.append(img)
img.save(f"{frames_dir}/frame_{gf:04d}.png")
gf += 1
print(f"Rendered {gf} frames.")
# ── assemble GIF ──────────────────────────────────────────────────────────────
gif_path = "/home/daytona/workspace/alphafold-animation/alphafold3_diffusion.gif"
# Convert to palette-mode for smaller GIF
palette_frames = []
for img in all_pil:
palette_frames.append(img.quantize(colors=256, method=Image.Quantize.FASTOCTREE))
palette_frames[0].save(
gif_path,
save_all=True,
append_images=palette_frames[1:],
duration=83, # ~12 fps
loop=0,
optimize=False,
)
size_mb = os.path.getsize(gif_path) / 1e6
print(f"GIF saved: {gif_path} ({size_mb:.1f} MB, {gf} frames)")
print("DONE")
cd /home/daytona/workspace/alphafold-animation && python3 make_gif.py 2>&1
alphafold3 diffusion
GIF File · GIF
file
File ·
| # | Scene | Frames |
|---|---|---|
| 1 | Title card with starfield fade-in | 10 |
| 2 | 1D amino acid sequence → arrow → 3D blob | 14 |
| 3 | 3 input modalities boxes (Protein / DNA-RNA / Ligand) fade in | 16 |
| 4 | MSA matrix + Pair Representation matrix + triangle inequality | 18 |
| 5 | 5-stage noise ladder (clean → pure noise) with training arrow | 18 |
| 6 | Denoising animation - atom cloud resolves into helix with progress bar | 24 |
| 7 | Ribbon structure builds up with side chains + labels | 18 |
| 8 | 6 application circles pop in (Drug Discovery, Ligand Binding...) | 18 |
| 9 | End card with orbiting atom dots | 16 |