https://www.youtube.com/watch?v=3gSy_yN9YBo&t=1s https://www.youtube.com/watch?v=CYncNBMPLLk by using this reference alphafold video , can you make new video animated which easily explaine
AlphaFold2 pipeline Evoformer MSA structure module explained research
mkdir -p /home/daytona/workspace/alphafold-animation && ls /home/daytona/workspa…
~/alphafold-animation/alphafold_animation.py
"""
AlphaFold2 Explained - Animated Video for Research Scientists
Covers: Protein Folding Problem -> Input -> MSA -> Evoformer -> Structure Module -> Recycling -> Output
"""
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 FancyArrowPatch, FancyBboxPatch, Circle, Arc, Wedge
from matplotlib.animation import FuncAnimation, FFMpegWriter
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.gridspec as gridspec
import warnings
warnings.filterwarnings('ignore')
# ── Color palette ──────────────────────────────────────────────────────────
BG = '#0d1117'
PANEL = '#161b22'
ACCENT1 = '#58a6ff' # blue – sequence / MSA
ACCENT2 = '#3fb950' # green – Evoformer
ACCENT3 = '#f78166' # red – pair representation
ACCENT4 = '#e3b341' # gold – structure module
ACCENT5 = '#bc8cff' # purple – recycling
WHITE = '#e6edf3'
GRAY = '#8b949e'
DARK_GRAY= '#21262d'
def make_fig():
fig = plt.figure(figsize=(16, 9), facecolor=BG, dpi=120)
return fig
def clear_axes(ax):
ax.cla()
ax.set_facecolor(BG)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
def title_text(ax, text, y=0.93, size=24, color=WHITE, weight='bold'):
ax.text(0.5, y, text, ha='center', va='top', fontsize=size, color=color,
fontweight=weight, transform=ax.transAxes,
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
def subtitle_text(ax, text, y=0.07, size=12, color=GRAY):
ax.text(0.5, y, text, ha='center', va='bottom', fontsize=size, color=color,
transform=ax.transAxes, style='italic')
def progress_bar(ax, step, total=8, y=0.015, color=ACCENT1):
"""Draw a thin progress bar at the bottom."""
bar_w = 0.85
x0 = (1 - bar_w) / 2
ax.add_patch(FancyBboxPatch((x0, y), bar_w, 0.012,
boxstyle='round,pad=0.002', linewidth=0,
facecolor=DARK_GRAY, transform=ax.transAxes, clip_on=False))
fill = bar_w * (step / total)
ax.add_patch(FancyBboxPatch((x0, y), fill, 0.012,
boxstyle='round,pad=0.002', linewidth=0,
facecolor=color, transform=ax.transAxes, clip_on=False))
ax.text(0.5, y + 0.006, f'Step {step}/{total}',
ha='center', va='center', fontsize=8, color=WHITE,
transform=ax.transAxes)
# ══════════════════════════════════════════════════════════════════════════════
# SCENE DEFINITIONS (each returns a list of (func, n_frames) tuples)
# ══════════════════════════════════════════════════════════════════════════════
# ── Scene 1 : Title card ─────────────────────────────────────────────────────
def scene_title(fig, frame, total):
fig.clear()
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
# Animated background dots
rng = np.random.default_rng(42)
xs = rng.random(120)
ys = rng.random(120)
sizes = rng.uniform(2, 10, 120)
alphas = np.clip(np.sin(xs * 20 + t * 6) * 0.4 + 0.3, 0.05, 0.7)
for x, y, s, a in zip(xs, ys, sizes, alphas):
ax.plot(x, y, 'o', markersize=s, color=ACCENT1, alpha=a,
transform=ax.transAxes)
# Fade-in alpha
fade = min(1.0, t * 3)
ax.text(0.5, 0.62, 'AlphaFold2', ha='center', va='center',
fontsize=54, color=ACCENT1, fontweight='bold',
alpha=fade, transform=ax.transAxes,
path_effects=[pe.withStroke(linewidth=6, foreground='#1a3a5c')])
ax.text(0.5, 0.50, 'How AI Predicts Protein Structure', ha='center',
va='center', fontsize=22, color=WHITE, alpha=fade,
transform=ax.transAxes)
ax.text(0.5, 0.40, 'From Amino Acid Sequence → 3D Atomic Coordinates',
ha='center', va='center', fontsize=14, color=GRAY, alpha=fade,
transform=ax.transAxes, style='italic')
# Bottom credit
ax.text(0.5, 0.08, 'Based on Jumper et al., Nature 2021 | AlphaFold2 Architecture',
ha='center', va='center', fontsize=10, color=GRAY, alpha=fade * 0.8,
transform=ax.transAxes)
# ── Scene 2 : The Protein Folding Problem ────────────────────────────────────
def scene_folding_problem(fig, frame, total):
fig.clear()
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'The Protein Folding Problem', y=0.95, size=26)
progress_bar(ax, 1)
# Left panel: 1D → 3D concept
# Draw amino acid chain (1D)
colors_aa = [ACCENT1, ACCENT2, ACCENT3, ACCENT4, ACCENT5, ACCENT1, ACCENT2, ACCENT3]
aa_labels = ['Met', 'Gly', 'Ala', 'Val', 'Leu', 'Ile', 'Pro', 'Phe']
n = len(colors_aa)
ax.text(0.22, 0.82, '1D: Amino Acid Sequence', ha='center', fontsize=13,
color=WHITE, fontweight='bold', transform=ax.transAxes)
for i, (c, lbl) in enumerate(zip(colors_aa, aa_labels)):
x = 0.06 + i * 0.033
y = 0.72
circle = Circle((x, y), 0.013, color=c, transform=ax.transAxes, zorder=5)
ax.add_patch(circle)
ax.text(x, y, lbl[:1], ha='center', va='center', fontsize=7,
color=BG, fontweight='bold', transform=ax.transAxes, zorder=6)
if i < n - 1:
ax.annotate('', xy=(x + 0.033 - 0.013, y), xytext=(x + 0.013, y),
arrowprops=dict(arrowstyle='->', color=GRAY, lw=1.5),
xycoords='axes fraction', textcoords='axes fraction')
# Arrow down
arrow_alpha = min(1.0, t * 4)
ax.annotate('', xy=(0.22, 0.54), xytext=(0.22, 0.64),
arrowprops=dict(arrowstyle='->', color=ACCENT1, lw=3),
xycoords='axes fraction', textcoords='axes fraction',
annotation_clip=False)
ax.text(0.28, 0.59, '???', ha='left', fontsize=18, color=ACCENT3,
fontweight='bold', transform=ax.transAxes, alpha=arrow_alpha)
# 3D structure hint (coil approximation)
ax.text(0.22, 0.51, '3D: Folded Structure', ha='center', fontsize=13,
color=WHITE, fontweight='bold', transform=ax.transAxes)
theta = np.linspace(0, 4 * np.pi, 200)
cx = 0.22 + 0.09 * np.cos(theta) * np.linspace(0.3, 1, 200)
cy = 0.32 + 0.10 * np.sin(theta) * np.linspace(0.3, 1, 200)
reveal = int(len(cx) * min(1.0, t * 2.5))
if reveal > 1:
# Color by position
for j in range(0, reveal - 1, 3):
frac = j / len(cx)
col = plt.cm.cool(frac)
ax.plot(cx[j:j+4], cy[j:j+4], lw=3, color=col,
transform=ax.transAxes, solid_capstyle='round')
# Right panel: Why it matters
if t > 0.25:
ax.add_patch(FancyBboxPatch((0.48, 0.20), 0.49, 0.68,
boxstyle='round,pad=0.01', linewidth=1.5,
edgecolor=ACCENT2, facecolor=DARK_GRAY,
transform=ax.transAxes))
ax.text(0.725, 0.84, 'Why Does This Matter?', ha='center', fontsize=14,
color=ACCENT2, fontweight='bold', transform=ax.transAxes)
points = [
(ACCENT1, 'Function follows structure'),
(ACCENT2, '~200M known sequences, <200K solved structures'),
(ACCENT3, 'Experimental methods: months–years per protein'),
(ACCENT4, 'Drug targets, enzyme engineering, disease mechanisms'),
(ACCENT5, 'Levinthal paradox: 10^300 possible conformations'),
]
for idx, (c, txt) in enumerate(points):
yt = 0.75 - idx * 0.11
if t > 0.25 + idx * 0.10:
ax.plot(0.51, yt, 'o', color=c, markersize=8,
transform=ax.transAxes)
ax.text(0.535, yt, txt, va='center', fontsize=10.5, color=WHITE,
transform=ax.transAxes)
subtitle_text(ax, 'AlphaFold2 solved this 50-year grand challenge in biology')
# ── Scene 3 : Input & MSA ────────────────────────────────────────────────────
def scene_msa(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'Step 1 – Input & Multiple Sequence Alignment (MSA)', size=20)
progress_bar(ax, 2)
# Input sequence
seq = list('MGLSDGEWQLVLNVWGKVEADIPGHGQEVLIRLFTGHPETLEKFDKFKHLKSEDEMKASEDLKKHGATVLTALGGILKKKGHHEAELKPLAQSHATKHKIPVKYLEFISECIIQVLQSKHPGDFGADAQGAMNKALELFRKDMASNYKELGFQG')
short = seq[:20]
ax.text(0.04, 0.88, 'Input: Query Sequence', fontsize=13, color=ACCENT1,
fontweight='bold', transform=ax.transAxes)
aa_colors_map = {'M': '#e3b341', 'G': '#58a6ff', 'A': '#3fb950', 'V': '#bc8cff',
'L': '#f78166', 'I': '#79c0ff', 'P': '#ffa657', 'F': '#ff7b72',
'W': '#7ee787', 'K': '#d2a8ff', 'R': '#ff7b72', 'H': '#ffa657',
'D': '#58a6ff', 'E': '#3fb950', 'N': '#e3b341', 'Q': '#bc8cff',
'S': '#79c0ff', 'T': '#7ee787', 'C': '#ffb3b3', 'Y': '#ffc8c8'}
for i, aa in enumerate(short):
x = 0.04 + i * 0.046
y = 0.80
c = aa_colors_map.get(aa, ACCENT1)
ax.add_patch(FancyBboxPatch((x - 0.017, y - 0.022), 0.034, 0.044,
boxstyle='round,pad=0.003', facecolor=c,
edgecolor='none', transform=ax.transAxes))
ax.text(x, y, aa, ha='center', va='center', fontsize=7.5, color=BG,
fontweight='bold', transform=ax.transAxes)
# MSA grid
msa_seqs = [
list('MGLSDGEWQLVLNVWGKVEADIPGHGQ'),
list('MGLSDGQWQLVLNVWGKVEADLPGSGQ'),
list('MGLSEGEWRLLLDVWGKVQADIPGYGER'),
list('--LSDGEWQLVLNVWGKVEAD-PGHGQE'),
list('MGLSDGEWHLVLNTWGKVEADIPGHGQE'),
list('MGLSD-EWQLVLNVWGKVEADIPGHGQ-'),
list('MGLADGEWQLILNVWGKVEADIPGHGQE'),
list('MGLSDGEWQLVLNVWGKV-ADIPGHGQE'),
]
organism_labels = ['Human Hb-α', 'Chimp Hb-α', 'Horse Hb-β', 'Whale Mb',
'Chicken Hb', 'Yeast Lgb', 'E. coli HbO', 'Sea Urchin']
if t > 0.15:
ax.text(0.04, 0.69, 'MSA: Evolutionary Homologs from Sequence Databases',
fontsize=12, color=ACCENT2, fontweight='bold', transform=ax.transAxes)
n_show = min(len(msa_seqs), int(t * 30))
cols = 27
cell_w, cell_h = 0.034, 0.055
for row_i in range(min(n_show, len(msa_seqs))):
for col_j in range(cols):
aa = msa_seqs[row_i][col_j] if col_j < len(msa_seqs[row_i]) else '-'
x = 0.04 + col_j * cell_w
y = 0.62 - row_i * (cell_h + 0.003)
c = aa_colors_map.get(aa, DARK_GRAY)
if aa == '-':
c = DARK_GRAY
alpha_v = 0.9 if row_i == 0 else 0.7
ax.add_patch(FancyBboxPatch((x - 0.013, y - 0.020), 0.028, 0.040,
boxstyle='round,pad=0.001', facecolor=c,
alpha=alpha_v, edgecolor='none',
transform=ax.transAxes))
ax.text(x, y, aa, ha='center', va='center', fontsize=6.5,
color=BG if aa != '-' else GRAY,
fontweight='bold', transform=ax.transAxes)
ax.text(0.04 + cols * cell_w + 0.005, 0.62 - row_i * (cell_h + 0.003),
organism_labels[row_i], va='center', fontsize=8, color=GRAY,
transform=ax.transAxes)
# Co-evolution annotation
if t > 0.55:
# Highlight coevolving columns
for col_j in [5, 12, 18]:
x = 0.04 + col_j * cell_w
ax.add_patch(FancyBboxPatch((x - 0.015, 0.14), 0.032, 0.51,
boxstyle='round,pad=0.002', facecolor='none',
edgecolor=ACCENT3, linewidth=1.5, linestyle='--',
transform=ax.transAxes, alpha=0.8))
ax.text(0.04, 0.11, 'Co-evolving residue pairs → spatial proximity constraints',
fontsize=10, color=ACCENT3, transform=ax.transAxes, style='italic')
# Pair representation hint
if t > 0.70:
ax.add_patch(FancyBboxPatch((0.02, 0.03), 0.95, 0.065,
boxstyle='round,pad=0.005', facecolor=DARK_GRAY,
edgecolor=ACCENT4, linewidth=1, transform=ax.transAxes))
ax.text(0.5, 0.063, 'MSA + Templates → Pair Representation (L×L distance matrix)',
ha='center', fontsize=10, color=ACCENT4, transform=ax.transAxes)
# ── Scene 4 : Evoformer ──────────────────────────────────────────────────────
def scene_evoformer(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'Step 2 – Evoformer: Dual-Track Transformer', size=22)
progress_bar(ax, 3)
# Two towers
tower_configs = [
(0.18, ACCENT1, 'MSA\nRepresentation', ['Row-wise\nMSA\nAttention', 'Column-wise\nAttention', 'Feed-\nForward']),
(0.62, ACCENT3, 'Pair\nRepresentation', ['Triangle\nUpdate\n(outgoing)', 'Triangle\nUpdate\n(incoming)', 'Pair\nTransition']),
]
for (tx, tc, tlbl, blocks) in tower_configs:
# Tower header
ax.add_patch(FancyBboxPatch((tx - 0.13, 0.76), 0.26, 0.14,
boxstyle='round,pad=0.008', facecolor=tc,
alpha=0.25, edgecolor=tc, linewidth=2,
transform=ax.transAxes))
ax.text(tx, 0.83, tlbl, ha='center', va='center', fontsize=13,
color=tc, fontweight='bold', transform=ax.transAxes)
# Blocks inside tower (animate one by one)
n_show = min(len(blocks), int(t * len(blocks) * 2.5 + 0.5))
for bi, blk in enumerate(blocks[:n_show]):
by = 0.61 - bi * 0.175
ax.add_patch(FancyBboxPatch((tx - 0.105, by - 0.06), 0.21, 0.12,
boxstyle='round,pad=0.006', facecolor=DARK_GRAY,
edgecolor=tc, linewidth=1.5,
transform=ax.transAxes))
ax.text(tx, by, blk, ha='center', va='center', fontsize=8.5,
color=WHITE, transform=ax.transAxes)
# Down arrows within tower
for bi in range(min(n_show - 1, len(blocks) - 1)):
by = 0.61 - bi * 0.175
ax.annotate('', xy=(tx, by - 0.075), xytext=(tx, by - 0.06),
arrowprops=dict(arrowstyle='->', color=tc, lw=1.5),
xycoords='axes fraction', textcoords='axes fraction')
# Cross-tower communication arrows (bidirectional)
if t > 0.45:
mid_y = 0.60
ax.annotate('', xy=(0.49, mid_y), xytext=(0.31, mid_y),
arrowprops=dict(arrowstyle='->', color=ACCENT2, lw=2.5,
connectionstyle='arc3,rad=-0.25'),
xycoords='axes fraction', textcoords='axes fraction')
ax.annotate('', xy=(0.31, mid_y - 0.09), xytext=(0.49, mid_y - 0.09),
arrowprops=dict(arrowstyle='->', color=ACCENT4, lw=2.5,
connectionstyle='arc3,rad=-0.25'),
xycoords='axes fraction', textcoords='axes fraction')
ax.text(0.40, mid_y + 0.03, 'Pair bias →\nMSA attention', ha='center',
fontsize=8, color=ACCENT2, transform=ax.transAxes)
ax.text(0.40, mid_y - 0.145, '← MSA → pair\nupdate', ha='center',
fontsize=8, color=ACCENT4, transform=ax.transAxes)
# 48 blocks badge
if t > 0.60:
ax.add_patch(FancyBboxPatch((0.77, 0.38), 0.20, 0.20,
boxstyle='round,pad=0.01', facecolor=ACCENT2,
alpha=0.2, edgecolor=ACCENT2, linewidth=2,
transform=ax.transAxes))
ax.text(0.87, 0.51, '×48', ha='center', va='center', fontsize=28,
color=ACCENT2, fontweight='bold', transform=ax.transAxes)
ax.text(0.87, 0.40, 'Evoformer\nBlocks', ha='center', va='center', fontsize=9,
color=WHITE, transform=ax.transAxes)
# Key insight box
if t > 0.75:
ax.add_patch(FancyBboxPatch((0.02, 0.03), 0.95, 0.10,
boxstyle='round,pad=0.006', facecolor=DARK_GRAY,
edgecolor=ACCENT5, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.5, 0.085, 'Key Insight: Continuous bidirectional information flow between evolutionary (MSA) and',
ha='center', fontsize=9, color=WHITE, transform=ax.transAxes)
ax.text(0.5, 0.045, 'spatial (pair) representations enables joint reasoning about structure and evolution',
ha='center', fontsize=9, color=ACCENT5, transform=ax.transAxes)
subtitle_text(ax, 'Evoformer = Evolutionary Transformer (48 stacked blocks)')
# ── Scene 5 : Attention & Triangle Update ────────────────────────────────────
def scene_triangle(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'Evoformer Deep Dive – Triangle Attention', size=22)
progress_bar(ax, 4)
# Left: pair representation matrix visualization
L = 8
ax.text(0.22, 0.88, 'Pair Representation Matrix (L×L)', ha='center',
fontsize=12, color=ACCENT3, fontweight='bold', transform=ax.transAxes)
rng = np.random.default_rng(99)
pair_matrix = rng.random((L, L))
pair_matrix = (pair_matrix + pair_matrix.T) / 2 # symmetric
np.fill_diagonal(pair_matrix, 1.0)
cell = 0.055
x0, y0 = 0.04, 0.24
cmap = plt.cm.YlOrRd
for i in range(L):
for j in range(L):
cx_c = x0 + j * cell + cell / 2
cy_c = y0 + (L - 1 - i) * cell + cell / 2
v = pair_matrix[i, j]
reveal_t = (i * L + j) / (L * L) * 0.6
if t > reveal_t:
ax.add_patch(FancyBboxPatch((x0 + j * cell, y0 + (L - 1 - i) * cell),
cell * 0.92, cell * 0.92,
boxstyle='round,pad=0.001',
facecolor=cmap(v), edgecolor='none',
transform=ax.transAxes))
ax.text(x0 - 0.015, y0 + (L - 1 - i) * cell + cell / 2,
f'i={i+1}', va='center', ha='right', fontsize=7, color=GRAY,
transform=ax.transAxes)
ax.text(x0 + i * cell + cell / 2, y0 - 0.018,
f'j={i+1}', ha='center', fontsize=7, color=GRAY,
transform=ax.transAxes)
# Highlight triangle i-k-j
if t > 0.40:
i_idx, k_idx, j_idx = 2, 5, 7
pts = [(x0 + i_idx * cell + cell / 2, y0 + (L - 1 - k_idx) * cell + cell / 2),
(x0 + k_idx * cell + cell / 2, y0 + (L - 1 - j_idx) * cell + cell / 2),
(x0 + j_idx * cell + cell / 2, y0 + (L - 1 - i_idx) * cell + cell / 2)]
from matplotlib.patches import Polygon
tri = Polygon(pts, closed=True, facecolor='none', edgecolor=ACCENT4,
linewidth=2.5, transform=ax.transAxes, zorder=10)
ax.add_patch(tri)
ax.text(0.22, 0.18, 'Triangle i–k–j:\nd(i,j) ≤ d(i,k) + d(k,j)', ha='center',
fontsize=10, color=ACCENT4, transform=ax.transAxes)
# Right: explanation
ax.add_patch(FancyBboxPatch((0.48, 0.08), 0.50, 0.78,
boxstyle='round,pad=0.01', facecolor=DARK_GRAY,
edgecolor=ACCENT1, linewidth=1.5,
transform=ax.transAxes))
sections = [
(ACCENT1, 0.82, 'Triangle Update Operations', 14, 'bold'),
(WHITE, 0.74, 'Enforce geometric consistency:', 10, 'normal'),
(ACCENT3, 0.68, '• If i↔k and k↔j are close, i↔j should be too', 9.5, 'normal'),
(WHITE, 0.62, 'Two flavours:', 10, 'normal'),
(ACCENT2, 0.56, ' "Outgoing" edges: i→k, j→k update i→j', 9.5, 'normal'),
(ACCENT4, 0.50, ' "Incoming" edges: k→i, k→j update i→j', 9.5, 'normal'),
(WHITE, 0.40, 'Row-wise MSA Attention:', 10, 'normal'),
(WHITE, 0.34, '• Attends across sequence positions within a row', 9.5, 'normal'),
(ACCENT1, 0.28, '• Pair bias term added to attention logits', 9.5, 'normal'),
(WHITE, 0.20, '• Allows MSA and pair tracks to exchange info', 9.5, 'normal'),
(ACCENT5, 0.12, 'Result: Physically-constrained representations', 9.5, 'bold'),
]
for (col, y, txt, sz, fw) in sections:
if t > 0.35:
ax.text(0.50, y, txt, va='center', fontsize=sz, color=col,
fontweight=fw, transform=ax.transAxes)
# ── Scene 6 : Structure Module ───────────────────────────────────────────────
def scene_structure_module(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'Step 3 – Structure Module & IPA', size=22)
progress_bar(ax, 5)
# Left: backbone rigid frames
ax.text(0.24, 0.88, 'Backbone as Rigid Frames (N-Cα-C)', ha='center',
fontsize=12, color=ACCENT4, fontweight='bold', transform=ax.transAxes)
# Draw a protein backbone as a chain of rigid-body frames
n_res = 10
theta_vals = np.linspace(0, 2 * np.pi, n_res, endpoint=False)
bx = 0.12 + 0.24 * (0.5 + 0.45 * np.cos(theta_vals))
by = 0.20 + 0.48 * (0.5 + 0.35 * np.sin(theta_vals))
reveal_n = max(2, int(t * (n_res + 3)))
for i in range(min(reveal_n, n_res)):
# Frame box
ax.add_patch(FancyBboxPatch((bx[i] - 0.025, by[i] - 0.025), 0.05, 0.05,
boxstyle='round,pad=0.003',
facecolor=ACCENT4, alpha=0.3,
edgecolor=ACCENT4, linewidth=1.5,
transform=ax.transAxes))
ax.text(bx[i], by[i], str(i + 1), ha='center', va='center', fontsize=7,
color=WHITE, transform=ax.transAxes)
if i > 0:
ax.annotate('', xy=(bx[i], by[i]), xytext=(bx[i - 1], by[i - 1]),
arrowprops=dict(arrowstyle='->', color=ACCENT2, lw=1.5),
xycoords='axes fraction', textcoords='axes fraction')
# Side chain hint
if t > 0.45 and i % 2 == 0:
sc_x = bx[i] + 0.03 * np.cos(theta_vals[i] + np.pi / 2)
sc_y = by[i] + 0.05 * np.sin(theta_vals[i] + np.pi / 2)
ax.plot([bx[i], sc_x], [by[i], sc_y], color=ACCENT5, lw=2,
transform=ax.transAxes)
ax.plot(sc_x, sc_y, 'o', color=ACCENT5, markersize=5,
transform=ax.transAxes)
# Right side: IPA + components
ax.add_patch(FancyBboxPatch((0.48, 0.08), 0.50, 0.78,
boxstyle='round,pad=0.01', facecolor=DARK_GRAY,
edgecolor=ACCENT4, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.73, 0.82, 'Structure Module Components', ha='center', fontsize=13,
color=ACCENT4, fontweight='bold', transform=ax.transAxes)
components = [
(ACCENT1, 0.73, 'Invariant Point Attention (IPA)'),
(WHITE, 0.67, '→ Attends in 3D space using Euclidean geometry'),
(WHITE, 0.61, '→ Invariant to global rotation/translation (SE(3))'),
(ACCENT2, 0.53, 'Backbone Update'),
(WHITE, 0.47, '→ Predict rotation + translation per residue'),
(WHITE, 0.41, '→ Black-hole initialization: all Cα at origin'),
(ACCENT3, 0.33, 'Torsion Angle Prediction'),
(WHITE, 0.27, '→ 7 torsion angles per residue (φ,ψ,χ1–χ5)'),
(WHITE, 0.21, '→ Defines full-atom side-chain positions'),
(ACCENT4, 0.13, '8 Structure Module blocks applied sequentially'),
(ACCENT5, 0.07, 'Output: 3D coordinates (x,y,z) for all atoms'),
]
for (col, y, txt) in components:
if t > 0.30:
fw = 'bold' if col != WHITE else 'normal'
ax.text(0.50, y, txt, va='center', fontsize=9.5, color=col,
fontweight=fw, transform=ax.transAxes)
subtitle_text(ax, 'IPA uses local 3D frames to enforce SE(3)-equivariance')
# ── Scene 7 : Recycling & Confidence ─────────────────────────────────────────
def scene_recycling(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'Step 4 – Recycling & Confidence (pLDDT / PAE)', size=21)
progress_bar(ax, 6)
# Recycling loop diagram
ax.text(0.28, 0.88, 'Recycling Loop (×3 iterations)', ha='center',
fontsize=13, color=ACCENT5, fontweight='bold', transform=ax.transAxes)
cycle_centers = [(0.12, 0.60), (0.28, 0.73), (0.44, 0.60), (0.28, 0.47)]
cycle_labels = ['Input\n(MSA+Pair)', 'Evoformer\n(48 blocks)', 'Structure\nModule', 'Refined\nStructure']
cycle_colors = [ACCENT1, ACCENT2, ACCENT4, ACCENT5]
for i, ((cx, cy), lbl, cc) in enumerate(zip(cycle_centers, cycle_labels, cycle_colors)):
show_t = i * 0.15
if t > show_t:
ax.add_patch(Circle((cx, cy), 0.065, color=cc, alpha=0.25,
transform=ax.transAxes, zorder=3))
ax.add_patch(Circle((cx, cy), 0.065, color=cc, alpha=0,
transform=ax.transAxes, linewidth=2.5,
fill=False, zorder=4))
circ2 = plt.Circle((cx, cy), 0.065, color=cc, fill=False,
linewidth=2.5, transform=ax.transAxes, zorder=4)
ax.add_patch(circ2)
ax.text(cx, cy, lbl, ha='center', va='center', fontsize=7.5,
color=WHITE, fontweight='bold', transform=ax.transAxes, zorder=5)
# Arrows
arrow_paths = [
(cycle_centers[0], cycle_centers[1]),
(cycle_centers[1], cycle_centers[2]),
(cycle_centers[2], cycle_centers[3]),
]
for i, (src, dst) in enumerate(arrow_paths):
if t > 0.35 + i * 0.15:
ax.annotate('', xy=dst, xytext=src,
arrowprops=dict(arrowstyle='->', color=ACCENT5, lw=2),
xycoords='axes fraction', textcoords='axes fraction')
# Feedback arrow
if t > 0.65:
ax.annotate('', xy=(0.12, 0.64), xytext=(0.28, 0.40),
arrowprops=dict(arrowstyle='->', color=ACCENT3, lw=2,
connectionstyle='arc3,rad=0.4'),
xycoords='axes fraction', textcoords='axes fraction')
ax.text(0.04, 0.52, 'Recycle\n×3', ha='center', fontsize=10,
color=ACCENT3, fontweight='bold', transform=ax.transAxes)
# pLDDT bar chart
if t > 0.40:
ax.add_patch(FancyBboxPatch((0.52, 0.40), 0.45, 0.46,
boxstyle='round,pad=0.01', facecolor=DARK_GRAY,
edgecolor=ACCENT1, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.745, 0.83, 'pLDDT – Per-residue Confidence', ha='center',
fontsize=11, color=ACCENT1, fontweight='bold', transform=ax.transAxes)
rng = np.random.default_rng(7)
plddt = np.clip(rng.normal(82, 15, 30), 10, 100)
plddt[:5] = np.clip(plddt[:5] * 0.5, 10, 50) # disordered region
plddt[-5:] = np.clip(plddt[-5:] * 0.6, 10, 55)
bar_colors = []
for v in plddt:
if v >= 90: bar_colors.append('#1f77b4')
elif v >= 70: bar_colors.append('#17becf')
elif v >= 50: bar_colors.append('#ffc107')
else: bar_colors.append('#d62728')
bar_x0 = 0.54
bar_y0 = 0.44
bar_w_total = 0.41
bw = bar_w_total / len(plddt)
bh_scale = 0.32
for bi, (v, bc) in enumerate(zip(plddt, bar_colors)):
reveal = bi / len(plddt) * 0.4 + 0.40
if t > reveal:
bh = (v / 100) * bh_scale
ax.add_patch(FancyBboxPatch((bar_x0 + bi * bw, bar_y0), bw * 0.9, bh,
boxstyle='square,pad=0', facecolor=bc,
edgecolor='none', transform=ax.transAxes))
ax.axhline(y=bar_y0 + 0.90 * bh_scale, xmin=0.54, xmax=0.95,
color='white', lw=0.8, linestyle='--', alpha=0.4)
ax.text(0.955, bar_y0 + 0.90 * bh_scale, '90', va='center', fontsize=7,
color=WHITE, transform=ax.transAxes)
# Legend
for lbl, lc, lx in [('≥90 (very high)', '#1f77b4', 0.54),
('70-90 (confident)', '#17becf', 0.68),
('50-70 (low)', '#ffc107', 0.795),
('<50 (very low)', '#d62728', 0.895)]:
ax.plot(lx, 0.415, 's', color=lc, markersize=7, transform=ax.transAxes)
ax.text(lx + 0.008, 0.415, lbl, va='center', fontsize=6.5, color=WHITE,
transform=ax.transAxes)
# PAE hint
if t > 0.75:
ax.add_patch(FancyBboxPatch((0.52, 0.08), 0.45, 0.28,
boxstyle='round,pad=0.01', facecolor=DARK_GRAY,
edgecolor=ACCENT3, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.745, 0.33, 'PAE – Predicted Aligned Error', ha='center',
fontsize=11, color=ACCENT3, fontweight='bold', transform=ax.transAxes)
ax.text(0.745, 0.26, 'Expected error in position of residue j', ha='center',
fontsize=9, color=WHITE, transform=ax.transAxes)
ax.text(0.745, 0.20, 'if the true frame for residue i were aligned', ha='center',
fontsize=9, color=WHITE, transform=ax.transAxes)
ax.text(0.745, 0.12, 'Low PAE between domains → confident relative positions',
ha='center', fontsize=9, color=ACCENT5, transform=ax.transAxes)
subtitle_text(ax, '3 recycling passes improve accuracy by ~10-15% GDT_TS')
# ── Scene 8 : Full Pipeline ───────────────────────────────────────────────────
def scene_pipeline(fig, frame, total):
fig.clear()
fig.set_facecolor(BG)
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
title_text(ax, 'AlphaFold2 – Complete Pipeline Overview', size=22)
progress_bar(ax, 7)
stages = [
(0.07, ACCENT1, '①\nInput\nSequence'),
(0.22, ACCENT2, '②\nDatabase\nSearch'),
(0.37, ACCENT1, '③\nMSA +\nTemplates'),
(0.52, ACCENT3, '④\nEvoformer\n×48'),
(0.67, ACCENT4, '⑤\nStructure\nModule ×8'),
(0.82, ACCENT5, '⑥\nRecycling\n×3'),
(0.94, '#7ee787', '⑦\n3D\nStructure'),
]
y_center = 0.60
box_h = 0.20
box_w = 0.10
for i, (sx, sc, slbl) in enumerate(stages):
reveal_t = i * 0.10
if t > reveal_t:
alpha_v = min(1.0, (t - reveal_t) / 0.10)
ax.add_patch(FancyBboxPatch((sx - box_w / 2, y_center - box_h / 2),
box_w, box_h,
boxstyle='round,pad=0.008', facecolor=sc,
alpha=0.30 * alpha_v, edgecolor=sc,
linewidth=2, transform=ax.transAxes))
ax.text(sx, y_center, slbl, ha='center', va='center', fontsize=8,
color=WHITE, fontweight='bold', transform=ax.transAxes,
alpha=alpha_v)
if i < len(stages) - 1:
next_sx = stages[i + 1][0]
reveal_t2 = (i + 0.7) * 0.10
if t > reveal_t2:
ax.annotate('', xy=(next_sx - box_w / 2, y_center),
xytext=(sx + box_w / 2, y_center),
arrowprops=dict(arrowstyle='->', color=GRAY, lw=2),
xycoords='axes fraction', textcoords='axes fraction')
# Performance callouts
if t > 0.65:
stats = [
(0.12, 0.35, ACCENT1, 'CASP14\nWinner'),
(0.35, 0.35, ACCENT2, '>200M structures\nin AF DB'),
(0.57, 0.35, ACCENT3, '93M\nparameters'),
(0.80, 0.35, ACCENT4, '~3 min / protein\n(A100 GPU)'),
]
for (sx, sy, sc, lbl) in stats:
ax.add_patch(FancyBboxPatch((sx - 0.09, sy - 0.07), 0.18, 0.14,
boxstyle='round,pad=0.006', facecolor=sc,
alpha=0.20, edgecolor=sc, linewidth=1.5,
transform=ax.transAxes))
ax.text(sx, sy, lbl, ha='center', va='center', fontsize=9.5,
color=WHITE, fontweight='bold', transform=ax.transAxes)
# Impact
if t > 0.80:
ax.add_patch(FancyBboxPatch((0.03, 0.04), 0.94, 0.12,
boxstyle='round,pad=0.006', facecolor=DARK_GRAY,
edgecolor=ACCENT2, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.5, 0.115, 'AlphaFold2 achieved median GDT_TS of 92.4 at CASP14 — comparable to experimental accuracy',
ha='center', fontsize=10, color=ACCENT2, fontweight='bold', transform=ax.transAxes)
ax.text(0.5, 0.065, 'Jumper et al., Nature 2021 | Nobel Prize in Chemistry 2024 (David Baker, Demis Hassabis, John Jumper)',
ha='center', fontsize=9, color=GRAY, transform=ax.transAxes)
# ── Scene 9 : Outro ───────────────────────────────────────────────────────────
def scene_outro(fig, frame, total):
fig.clear()
ax = fig.add_subplot(111)
ax.set_facecolor(BG)
ax.axis('off')
t = frame / total
# Background particles
rng = np.random.default_rng(13)
for _ in range(80):
x = rng.random()
y = rng.random()
c = rng.choice([ACCENT1, ACCENT2, ACCENT3, ACCENT4, ACCENT5])
a = rng.uniform(0.05, 0.25)
s = rng.uniform(3, 12)
ax.plot(x, y, 'o', color=c, alpha=a * t, markersize=s,
transform=ax.transAxes)
fade = min(1.0, t * 2)
ax.text(0.5, 0.72, 'Summary', ha='center', fontsize=36, color=ACCENT1,
fontweight='bold', alpha=fade, transform=ax.transAxes)
summary_lines = [
(ACCENT1, '1. Sequence → MSA (evolutionary co-variation)'),
(ACCENT3, '2. MSA + Templates → Pair Representation (spatial distances)'),
(ACCENT2, '3. Evoformer: 48 blocks of bidirectional attention (MSA ↔ Pair)'),
(ACCENT4, '4. Structure Module: IPA + torsion angles → 3D coordinates'),
(ACCENT5, '5. Recycling: 3 additional passes for refinement'),
('#7ee787', '6. pLDDT & PAE: per-residue and inter-domain confidence'),
]
for i, (c, txt) in enumerate(summary_lines):
delay = 0.1 + i * 0.08
a = max(0.0, min(1.0, (t - delay) / 0.12))
ax.text(0.12, 0.60 - i * 0.088, txt, va='center', fontsize=12,
color=c, alpha=a, transform=ax.transAxes)
if t > 0.80:
ax.text(0.5, 0.05, 'Jumper et al. (2021) Nature 596, 583–589 | Nobel Chemistry 2024',
ha='center', fontsize=10, color=GRAY, alpha=fade, transform=ax.transAxes)
# ══════════════════════════════════════════════════════════════════════════════
# ANIMATION DRIVER
# ══════════════════════════════════════════════════════════════════════════════
scenes = [
(scene_title, 50),
(scene_folding_problem, 70),
(scene_msa, 80),
(scene_evoformer, 80),
(scene_triangle, 70),
(scene_structure_module, 70),
(scene_recycling, 80),
(scene_pipeline, 80),
(scene_outro, 60),
]
total_frames = sum(n for _, n in scenes)
def build_scene_map():
"""Pre-build a lookup: global_frame -> (scene_fn, local_frame, scene_total)"""
sm = []
for fn, n in scenes:
for f in range(n):
sm.append((fn, f, n))
return sm
scene_map = build_scene_map()
fig = make_fig()
def animate(global_frame):
fn, local_f, n = scene_map[global_frame]
fn(fig, local_f, n)
print(f"Total frames: {total_frames}")
print("Rendering animation...")
anim = FuncAnimation(fig, animate, frames=total_frames, interval=60, blit=False)
output_path = '/home/daytona/workspace/alphafold-animation/alphafold_explained.mp4'
try:
writer = FFMpegWriter(fps=18, metadata={'title': 'AlphaFold2 Explained'},
bitrate=2400,
extra_args=['-vcodec', 'libx264', '-pix_fmt', 'yuv420p'])
anim.save(output_path, writer=writer, dpi=120)
print(f"Saved: {output_path}")
except Exception as e:
print(f"MP4 failed: {e}. Trying GIF...")
gif_path = '/home/daytona/workspace/alphafold-animation/alphafold_explained.gif'
from matplotlib.animation import PillowWriter
writer_gif = PillowWriter(fps=15)
anim.save(gif_path, writer=writer_gif, dpi=100)
print(f"Saved GIF: {gif_path}")
plt.close(fig)
print("Done.")
cd /home/daytona/workspace/alphafold-animation && python alphafold_animation.py …
~/alphafold-animation/alphafold_animation_v2.py
"""
AlphaFold2 Explained - Animated GIF for Research Scientists
Lightweight version: fewer frames, Pillow GIF writer
"""
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
from matplotlib.animation import FuncAnimation, PillowWriter
import warnings
warnings.filterwarnings('ignore')
# ── Palette ────────────────────────────────────────────────────────────────
BG = '#0d1117'
PANEL = '#161b22'
ACCENT1 = '#58a6ff'
ACCENT2 = '#3fb950'
ACCENT3 = '#f78166'
ACCENT4 = '#e3b341'
ACCENT5 = '#bc8cff'
WHITE = '#e6edf3'
GRAY = '#8b949e'
DARK_GRAY= '#21262d'
def clear_ax(ax):
ax.cla()
ax.set_facecolor(BG)
ax.set_xlim(0, 1); ax.set_ylim(0, 1)
ax.axis('off')
def ttl(ax, text, y=0.95, sz=20, col=WHITE):
ax.text(0.5, y, text, ha='center', va='top', fontsize=sz, color=col,
fontweight='bold', transform=ax.transAxes,
path_effects=[pe.withStroke(linewidth=3, foreground=BG)])
def pbar(ax, step, total=8):
bw = 0.86; x0 = (1 - bw) / 2; y = 0.015
ax.add_patch(FancyBboxPatch((x0, y), bw, 0.011, boxstyle='round,pad=0.002',
linewidth=0, facecolor=DARK_GRAY, transform=ax.transAxes))
ax.add_patch(FancyBboxPatch((x0, y), bw * step / total, 0.011, boxstyle='round,pad=0.002',
linewidth=0, facecolor=ACCENT1, transform=ax.transAxes))
ax.text(0.5, y + 0.005, f'Step {step}/{total}', ha='center', va='center',
fontsize=7, color=WHITE, transform=ax.transAxes)
# ══════════════════════════ SCENE FUNCTIONS ═══════════════════════════════
def s_title(ax, t):
clear_ax(ax)
rng = np.random.default_rng(42)
for x, y, s, ph in zip(rng.random(60), rng.random(60), rng.uniform(2,8,60), rng.uniform(0,6,60)):
ax.plot(x, y, 'o', ms=s, color=ACCENT1, alpha=np.clip(np.sin(ph + t*4)*0.35+0.25,0.05,0.6),
transform=ax.transAxes)
f = min(1.0, t * 3)
ax.text(0.5, 0.62, 'AlphaFold2', ha='center', fontsize=48, color=ACCENT1,
fontweight='bold', alpha=f, transform=ax.transAxes,
path_effects=[pe.withStroke(linewidth=5, foreground='#1a3a5c')])
ax.text(0.5, 0.50, 'How AI Predicts Protein Structure', ha='center',
fontsize=20, color=WHITE, alpha=f, transform=ax.transAxes)
ax.text(0.5, 0.41, 'Amino Acid Sequence → 3D Atomic Coordinates',
ha='center', fontsize=13, color=GRAY, alpha=f*0.9, transform=ax.transAxes, style='italic')
ax.text(0.5, 0.10, 'Jumper et al., Nature 2021 | Nobel Prize Chemistry 2024',
ha='center', fontsize=9.5, color=GRAY, alpha=f*0.75, transform=ax.transAxes)
def s_problem(ax, t):
clear_ax(ax)
ttl(ax, 'The Protein Folding Problem')
pbar(ax, 1)
aa_c = [ACCENT1, ACCENT2, ACCENT3, ACCENT4, ACCENT5, ACCENT1, ACCENT2, ACCENT3]
labels = ['M','G','A','V','L','I','P','F']
ax.text(0.22, 0.84, '1D: Amino Acid Sequence', ha='center', fontsize=11,
color=WHITE, fontweight='bold', transform=ax.transAxes)
for i,(c,l) in enumerate(zip(aa_c,labels)):
x = 0.06 + i*0.034; y = 0.75
ax.add_patch(Circle((x,y), 0.014, color=c, transform=ax.transAxes, zorder=5))
ax.text(x, y, l, ha='center', va='center', fontsize=8, color=BG,
fontweight='bold', transform=ax.transAxes, zorder=6)
if i < 7:
ax.annotate('', xy=(x+0.021,y), xytext=(x+0.014,y),
arrowprops=dict(arrowstyle='->', color=GRAY, lw=1.2),
xycoords='axes fraction', textcoords='axes fraction')
ax.annotate('', xy=(0.22,0.55), xytext=(0.22,0.66),
arrowprops=dict(arrowstyle='->', color=ACCENT1, lw=3),
xycoords='axes fraction', textcoords='axes fraction')
ax.text(0.28, 0.60, '?', ha='left', fontsize=22, color=ACCENT3, fontweight='bold',
transform=ax.transAxes, alpha=min(1,t*3))
ax.text(0.22, 0.52, '3D: Folded Structure', ha='center', fontsize=11,
color=WHITE, fontweight='bold', transform=ax.transAxes)
theta = np.linspace(0,4*np.pi,200)
cx2 = 0.22 + 0.09*np.cos(theta)*np.linspace(0.3,1,200)
cy2 = 0.32 + 0.10*np.sin(theta)*np.linspace(0.3,1,200)
rev = int(len(cx2) * min(1.0, t*2.2))
if rev > 2:
for j in range(0,rev-1,3):
col = plt.cm.cool(j/len(cx2))
ax.plot(cx2[j:j+4], cy2[j:j+4], lw=2.5, color=col,
transform=ax.transAxes, solid_capstyle='round')
if t > 0.25:
ax.add_patch(FancyBboxPatch((0.47,0.22),0.50,0.66, boxstyle='round,pad=0.01',
linewidth=1.5, edgecolor=ACCENT2, facecolor=DARK_GRAY,
transform=ax.transAxes))
ax.text(0.72, 0.83, 'Why This Matters', ha='center', fontsize=13,
color=ACCENT2, fontweight='bold', transform=ax.transAxes)
pts = [(ACCENT1,'Function follows structure'),
(ACCENT2,'~200M sequences, <200K solved structures'),
(ACCENT3,'Experimental: months–years per protein'),
(ACCENT4,'Levinthal paradox: 10^300 conformations'),
(ACCENT5,'Drug targets, disease mechanisms')]
for idx,(c,txt) in enumerate(pts):
if t > 0.25+idx*0.10:
ax.plot(0.50, 0.74-idx*0.10, 'o', color=c, ms=7, transform=ax.transAxes)
ax.text(0.52, 0.74-idx*0.10, txt, va='center', fontsize=9.5,
color=WHITE, transform=ax.transAxes)
def s_msa(ax, t):
clear_ax(ax)
ttl(ax, 'Step 1 – Multiple Sequence Alignment (MSA)', sz=18)
pbar(ax, 2)
aa_colors_map = {'M':'#e3b341','G':'#58a6ff','A':'#3fb950','V':'#bc8cff',
'L':'#f78166','I':'#79c0ff','P':'#ffa657','F':'#ff7b72',
'W':'#7ee787','K':'#d2a8ff','R':'#ff7b72','H':'#ffa657',
'D':'#58a6ff','E':'#3fb950','N':'#e3b341','Q':'#bc8cff',
'S':'#79c0ff','T':'#7ee787','C':'#ffb3b3','Y':'#ffc8c8'}
msa_seqs = [
list('MGLSDGEWQLVLNVWGKVEAD'),
list('MGLSDGQWQLVLNVWGKVEAD'),
list('MGLSEGEWRLLLDVWGKVQAD'),
list('--LSDGEWQLVLNVWGKVEAD'),
list('MGLSDGEWHLVLNTWGKVEAD'),
list('MGLSD-EWQLVLNVWGKVEAD'),
list('MGLADGEWQLILNVWGKVEAD'),
]
orgs = ['Human','Chimp','Horse','Whale','Chicken','Yeast','E. coli']
ax.text(0.04, 0.88, 'Query + Homologs from UniRef90, BFD, MGnify...', fontsize=10,
color=ACCENT1, transform=ax.transAxes)
cols = len(msa_seqs[0])
cell_w, cell_h = 0.040, 0.056
n_show = min(len(msa_seqs), max(1, int(t * len(msa_seqs) * 2.5)))
for ri in range(min(n_show, len(msa_seqs))):
for cj in range(cols):
aa = msa_seqs[ri][cj] if cj < len(msa_seqs[ri]) else '-'
xc = 0.04 + cj*cell_w; yc = 0.78 - ri*(cell_h+0.004)
c = aa_colors_map.get(aa, DARK_GRAY)
if aa == '-': c = DARK_GRAY
ax.add_patch(FancyBboxPatch((xc-0.015,yc-0.020),0.032,0.040,
boxstyle='round,pad=0.001', facecolor=c,
alpha=0.88, edgecolor='none', transform=ax.transAxes))
ax.text(xc,yc,aa,ha='center',va='center',fontsize=6.5,
color=BG if aa!='-' else GRAY, fontweight='bold', transform=ax.transAxes)
ax.text(0.04+cols*cell_w+0.006, 0.78-ri*(cell_h+0.004), orgs[ri],
va='center', fontsize=8, color=GRAY, transform=ax.transAxes)
if t > 0.55:
for cj in [5,12,18]:
xc = 0.04 + cj*cell_w
ax.add_patch(FancyBboxPatch((xc-0.017, 0.35), 0.034, 0.46,
boxstyle='round,pad=0.002', facecolor='none',
edgecolor=ACCENT3, linewidth=1.5, linestyle='--',
transform=ax.transAxes, alpha=0.8))
ax.text(0.04, 0.30, 'Co-evolving columns → constrained spatial proximity (pair representation)',
fontsize=9.5, color=ACCENT3, transform=ax.transAxes, style='italic')
if t > 0.72:
ax.add_patch(FancyBboxPatch((0.02,0.03),0.95,0.065, boxstyle='round,pad=0.005',
facecolor=DARK_GRAY, edgecolor=ACCENT4, linewidth=1,
transform=ax.transAxes))
ax.text(0.5, 0.063, 'MSA → L×L Pair Representation (pairwise residue distances)',
ha='center', fontsize=10, color=ACCENT4, transform=ax.transAxes)
def s_evoformer(ax, t):
clear_ax(ax)
ttl(ax, 'Step 2 – Evoformer (48 blocks)', sz=20)
pbar(ax, 3)
towers = [
(0.20, ACCENT1, 'MSA Tower',
['Row-wise MSA\nAttention', 'Column-wise\nAttention', 'Feed-Forward\nNetwork']),
(0.62, ACCENT3, 'Pair Tower',
['Triangle Update\n(outgoing)', 'Triangle Update\n(incoming)', 'Pair\nTransition']),
]
for (tx,tc,tlbl,blocks) in towers:
ax.add_patch(FancyBboxPatch((tx-0.14,0.76),0.28,0.14, boxstyle='round,pad=0.008',
facecolor=tc, alpha=0.22, edgecolor=tc, linewidth=2,
transform=ax.transAxes))
ax.text(tx, 0.83, tlbl, ha='center', va='center', fontsize=12,
color=tc, fontweight='bold', transform=ax.transAxes)
n_show = min(len(blocks), max(0, int(t*len(blocks)*2.5+0.3)))
for bi,blk in enumerate(blocks[:n_show]):
by = 0.61 - bi*0.175
ax.add_patch(FancyBboxPatch((tx-0.11,by-0.062),0.22,0.124,
boxstyle='round,pad=0.006', facecolor=DARK_GRAY,
edgecolor=tc, linewidth=1.5, transform=ax.transAxes))
ax.text(tx,by,blk,ha='center',va='center',fontsize=8.5,
color=WHITE, transform=ax.transAxes)
if bi < n_show-1:
ax.annotate('',xy=(tx,by-0.078),xytext=(tx,by-0.062),
arrowprops=dict(arrowstyle='->',color=tc,lw=1.5),
xycoords='axes fraction',textcoords='axes fraction')
if t > 0.48:
for my, rad, col, lbl in [(0.62,-0.25,ACCENT2,'Pair bias →\nMSA attention'),
(0.50, 0.25,ACCENT4,'← MSA informs\npair update')]:
ax.annotate('',xy=(0.48,my),xytext=(0.34,my),
arrowprops=dict(arrowstyle='->',color=col,lw=2.5,
connectionstyle=f'arc3,rad={rad}'),
xycoords='axes fraction',textcoords='axes fraction')
ax.text(0.41, my+0.035 if rad<0 else my-0.065, lbl, ha='center', fontsize=7.5,
color=col, transform=ax.transAxes)
if t > 0.65:
ax.add_patch(FancyBboxPatch((0.78,0.38),0.19,0.20, boxstyle='round,pad=0.01',
facecolor=ACCENT2, alpha=0.18, edgecolor=ACCENT2,
linewidth=2, transform=ax.transAxes))
ax.text(0.875,0.51,'×48',ha='center',va='center',fontsize=26,
color=ACCENT2, fontweight='bold', transform=ax.transAxes)
ax.text(0.875,0.41,'blocks',ha='center',va='center',fontsize=9,
color=WHITE, transform=ax.transAxes)
if t > 0.78:
ax.add_patch(FancyBboxPatch((0.02,0.03),0.95,0.10, boxstyle='round,pad=0.006',
facecolor=DARK_GRAY, edgecolor=ACCENT5, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.5,0.085,'Key insight: bidirectional attention between evolutionary (MSA) and spatial (pair) representations',
ha='center', fontsize=9.5, color=WHITE, transform=ax.transAxes)
ax.text(0.5,0.045,'enables joint reasoning about structure and evolution simultaneously',
ha='center', fontsize=9.5, color=ACCENT5, transform=ax.transAxes)
def s_structure(ax, t):
clear_ax(ax)
ttl(ax, 'Step 3 – Structure Module & IPA', sz=20)
pbar(ax, 4)
ax.text(0.22, 0.88, 'Backbone as Rigid Frames', ha='center', fontsize=11,
color=ACCENT4, fontweight='bold', transform=ax.transAxes)
n_res = 10
theta_v = np.linspace(0, 2*np.pi, n_res, endpoint=False)
bx = 0.12 + 0.24*(0.5 + 0.45*np.cos(theta_v))
by = 0.20 + 0.52*(0.5 + 0.35*np.sin(theta_v))
reveal_n = max(2, int(t*(n_res+3)))
for i in range(min(reveal_n, n_res)):
ax.add_patch(FancyBboxPatch((bx[i]-0.028,by[i]-0.028),0.056,0.056,
boxstyle='round,pad=0.004', facecolor=ACCENT4,
alpha=0.30, edgecolor=ACCENT4, linewidth=1.5,
transform=ax.transAxes))
ax.text(bx[i], by[i], str(i+1), ha='center', va='center', fontsize=7.5,
color=WHITE, transform=ax.transAxes)
if i > 0:
ax.annotate('',xy=(bx[i],by[i]),xytext=(bx[i-1],by[i-1]),
arrowprops=dict(arrowstyle='->',color=ACCENT2,lw=1.5),
xycoords='axes fraction',textcoords='axes fraction')
if t > 0.45 and i%2==0:
sc_x = bx[i]+0.032*np.cos(theta_v[i]+np.pi/2)
sc_y = by[i]+0.055*np.sin(theta_v[i]+np.pi/2)
ax.plot([bx[i],sc_x],[by[i],sc_y],color=ACCENT5,lw=2,transform=ax.transAxes)
ax.plot(sc_x,sc_y,'o',color=ACCENT5,ms=5,transform=ax.transAxes)
ax.add_patch(FancyBboxPatch((0.48,0.08),0.50,0.78, boxstyle='round,pad=0.01',
facecolor=DARK_GRAY, edgecolor=ACCENT4, linewidth=1.5,
transform=ax.transAxes))
ax.text(0.73, 0.82, 'Structure Module (×8 blocks)', ha='center', fontsize=12,
color=ACCENT4, fontweight='bold', transform=ax.transAxes)
rows = [(ACCENT1,0.73,'Invariant Point Attention (IPA)'),
(WHITE, 0.67,'→ Attends in 3D, SE(3)-invariant'),
(WHITE, 0.61,'→ Uses local backbone frames'),
(ACCENT2,0.53,'Backbone Update'),
(WHITE, 0.47,'→ Predict rotation + translation per residue'),
(WHITE, 0.41,'→ Black-hole init: all Cα at origin'),
(ACCENT3,0.33,'Torsion Angle Prediction'),
(WHITE, 0.27,'→ 7 angles per residue (φ,ψ,χ1-χ5)'),
(WHITE, 0.21,'→ Places full-atom side chains'),
(ACCENT4,0.13,'Output: (x,y,z) for every atom'),
(ACCENT5,0.07,'+ confidence scores (pLDDT, PAE)')]
if t > 0.30:
for (c,y,txt) in rows:
ax.text(0.50,y,txt,va='center',fontsize=9.5,color=c,
fontweight='bold' if c!=WHITE else 'normal',
transform=ax.transAxes)
def s_recycling(ax, t):
clear_ax(ax)
ttl(ax, 'Step 4 – Recycling & Confidence Scores', sz=20)
pbar(ax, 5)
centers = [(0.12,0.60),(0.28,0.73),(0.44,0.60),(0.28,0.47)]
labels = ['Input\n(MSA+Pair)','Evoformer\n×48','Structure\nModule','Refined\nCoords']
colors = [ACCENT1,ACCENT2,ACCENT4,ACCENT5]
for i,((cx,cy),lbl,cc) in enumerate(zip(centers,labels,colors)):
if t > i*0.15:
circ = Circle((cx,cy),0.065,color=cc,alpha=0.22,transform=ax.transAxes,zorder=3)
circ2 = Circle((cx,cy),0.065,color=cc,fill=False,linewidth=2.5,
transform=ax.transAxes,zorder=4)
ax.add_patch(circ); ax.add_patch(circ2)
ax.text(cx,cy,lbl,ha='center',va='center',fontsize=7.5,
color=WHITE,fontweight='bold',transform=ax.transAxes,zorder=5)
for i,((s,d)) in enumerate([(centers[0],centers[1]),(centers[1],centers[2]),(centers[2],centers[3])]):
if t > 0.35+i*0.12:
ax.annotate('',xy=d,xytext=s,
arrowprops=dict(arrowstyle='->',color=ACCENT5,lw=2),
xycoords='axes fraction',textcoords='axes fraction')
if t > 0.62:
ax.annotate('',xy=(0.12,0.645),xytext=(0.28,0.405),
arrowprops=dict(arrowstyle='->',color=ACCENT3,lw=2.2,
connectionstyle='arc3,rad=0.4'),
xycoords='axes fraction',textcoords='axes fraction')
ax.text(0.04,0.53,'Recycle\n×3',ha='center',fontsize=10,color=ACCENT3,
fontweight='bold',transform=ax.transAxes)
if t > 0.40:
ax.add_patch(FancyBboxPatch((0.52,0.42),0.45,0.44, boxstyle='round,pad=0.01',
facecolor=DARK_GRAY,edgecolor=ACCENT1,linewidth=1.5,
transform=ax.transAxes))
ax.text(0.745,0.82,'pLDDT – Per-residue Confidence',ha='center',fontsize=11,
color=ACCENT1,fontweight='bold',transform=ax.transAxes)
rng = np.random.default_rng(7)
plddt = np.clip(rng.normal(82,15,28),10,100)
plddt[:5] = np.clip(plddt[:5]*0.5,10,50)
plddt[-5:] = np.clip(plddt[-5:]*0.6,10,55)
bar_colors = ['#1f77b4' if v>=90 else '#17becf' if v>=70 else '#ffc107' if v>=50
else '#d62728' for v in plddt]
bx0=0.55; by0=0.46; bw_t=0.40; bh_s=0.30
bw = bw_t/len(plddt)
for bi,(v,bc) in enumerate(zip(plddt,bar_colors)):
if t > 0.40 + bi/len(plddt)*0.35:
bh = (v/100)*bh_s
ax.add_patch(FancyBboxPatch((bx0+bi*bw,by0),bw*0.88,bh,
boxstyle='square,pad=0',facecolor=bc,
edgecolor='none',transform=ax.transAxes))
for lbl,lc,lx in [('≥90','#1f77b4',0.55),('70-90','#17becf',0.65),
('50-70','#ffc107',0.76),('<50','#d62728',0.86)]:
ax.plot(lx,0.435,'s',color=lc,ms=7,transform=ax.transAxes)
ax.text(lx+0.008,0.435,lbl,va='center',fontsize=7.5,color=WHITE,transform=ax.transAxes)
if t > 0.72:
ax.add_patch(FancyBboxPatch((0.52,0.08),0.45,0.30, boxstyle='round,pad=0.01',
facecolor=DARK_GRAY,edgecolor=ACCENT3,linewidth=1.5,
transform=ax.transAxes))
ax.text(0.745,0.36,'PAE – Predicted Aligned Error',ha='center',fontsize=11,
color=ACCENT3,fontweight='bold',transform=ax.transAxes)
ax.text(0.745,0.28,'Error in residue j position given frame i is aligned',
ha='center',fontsize=8.5,color=WHITE,transform=ax.transAxes)
ax.text(0.745,0.20,'Low PAE between domains → confident relative domain positions',
ha='center',fontsize=8.5,color=ACCENT5,transform=ax.transAxes)
ax.text(0.745,0.12,'Low pLDDT → disordered / flexible region',
ha='center',fontsize=8.5,color=ACCENT4,transform=ax.transAxes)
def s_pipeline(ax, t):
clear_ax(ax)
ttl(ax, 'Complete AlphaFold2 Pipeline', sz=20)
pbar(ax, 6)
stages = [(0.08,ACCENT1,'①\nSequence'),
(0.22,ACCENT2,'②\nMSA'),
(0.36,ACCENT3,'③\nPair\nRepn.'),
(0.50,ACCENT2,'④\nEvoformer\n×48'),
(0.64,ACCENT4,'⑤\nStructure\nModule'),
(0.78,ACCENT5,'⑥\nRecycle\n×3'),
(0.92,'#7ee787','⑦\n3D\nStructure')]
yc=0.60; bh=0.20; bw=0.10
for i,(sx,sc,slbl) in enumerate(stages):
if t > i*0.10:
a = min(1.0,(t-i*0.10)/0.10)
ax.add_patch(FancyBboxPatch((sx-bw/2,yc-bh/2),bw,bh,
boxstyle='round,pad=0.008', facecolor=sc, alpha=0.28*a,
edgecolor=sc, linewidth=2, transform=ax.transAxes))
ax.text(sx,yc,slbl,ha='center',va='center',fontsize=8.5,color=WHITE,
fontweight='bold',transform=ax.transAxes,alpha=a)
if i < len(stages)-1:
next_sx = stages[i+1][0]
if t > (i+0.7)*0.10:
ax.annotate('',xy=(next_sx-bw/2,yc),xytext=(sx+bw/2,yc),
arrowprops=dict(arrowstyle='->',color=GRAY,lw=2),
xycoords='axes fraction',textcoords='axes fraction')
if t > 0.68:
stats = [(0.12,0.33,ACCENT1,'CASP14\nWinner'),(0.36,0.33,ACCENT2,'200M+\nstructures'),
(0.60,0.33,ACCENT3,'93M\nparameters'),(0.84,0.33,ACCENT4,'~3 min/protein\n(A100)')]
for (sx,sy,sc,lbl) in stats:
ax.add_patch(FancyBboxPatch((sx-0.09,sy-0.07),0.18,0.14,
boxstyle='round,pad=0.006',facecolor=sc,alpha=0.18,
edgecolor=sc,linewidth=1.5,transform=ax.transAxes))
ax.text(sx,sy,lbl,ha='center',va='center',fontsize=9.5,color=WHITE,
fontweight='bold',transform=ax.transAxes)
if t > 0.82:
ax.add_patch(FancyBboxPatch((0.02,0.04),0.95,0.12,
boxstyle='round,pad=0.006',facecolor=DARK_GRAY,
edgecolor=ACCENT2,linewidth=1.5,transform=ax.transAxes))
ax.text(0.5,0.117,'GDT_TS = 92.4 at CASP14 – comparable to experimental accuracy',
ha='center',fontsize=10.5,color=ACCENT2,fontweight='bold',transform=ax.transAxes)
ax.text(0.5,0.063,'Nobel Prize in Chemistry 2024 – Hassabis, Jumper (AlphaFold), Baker (Protein Design)',
ha='center',fontsize=9,color=GRAY,transform=ax.transAxes)
def s_outro(ax, t):
clear_ax(ax)
rng = np.random.default_rng(55)
for x,y,c,a,s in zip(rng.random(70),rng.random(70),
[ACCENT1,ACCENT2,ACCENT3,ACCENT4,ACCENT5]*14,
rng.uniform(0.04,0.22,70), rng.uniform(2,10,70)):
ax.plot(x,y,'o',color=c,alpha=a*min(1,t*2),ms=s,transform=ax.transAxes)
f = min(1,t*2)
ax.text(0.5,0.76,'Key Takeaways',ha='center',fontsize=32,color=ACCENT1,
fontweight='bold',alpha=f,transform=ax.transAxes)
summ = [(ACCENT1,'1. Sequence → MSA (evolutionary co-variation)'),
(ACCENT3,'2. MSA + Templates → L×L Pair Representation'),
(ACCENT2,'3. Evoformer: 48 blocks, bidirectional MSA ↔ Pair attention'),
(ACCENT4,'4. Structure Module: IPA + torsion angles → 3D coords (×8)'),
(ACCENT5,'5. Recycling: 3 additional passes for iterative refinement'),
('#7ee787','6. pLDDT + PAE: per-residue and inter-domain confidence')]
for i,(c,txt) in enumerate(summ):
a2 = max(0.0, min(1.0, (t-0.10-i*0.08)/0.10))
ax.text(0.10, 0.63-i*0.09, txt, va='center', fontsize=11.5, color=c,
alpha=a2, transform=ax.transAxes)
if t > 0.82:
ax.text(0.5,0.06,'Jumper et al. (2021) Nature 596, 583–589 | doi:10.1038/s41586-021-03819-2',
ha='center',fontsize=9.5,color=GRAY,alpha=f,transform=ax.transAxes)
# ══════════════════════════ BUILD ANIMATION ═══════════════════════════════
scenes = [
(s_title, 28),
(s_problem, 34),
(s_msa, 36),
(s_evoformer,36),
(s_structure,34),
(s_recycling,36),
(s_pipeline, 36),
(s_outro, 28),
]
total_frames = sum(n for _,n in scenes)
scene_map = []
for fn,n in scenes:
for f in range(n):
scene_map.append((fn, f, n))
print(f"Total frames: {total_frames}")
fig, ax = plt.subplots(figsize=(16, 9), facecolor=BG, dpi=100)
def animate(i):
fn, lf, n = scene_map[i]
fn(ax, lf / n)
return []
anim = FuncAnimation(fig, animate, frames=total_frames, interval=80, blit=False)
out_gif = '/home/daytona/workspace/alphafold-animation/alphafold_explained.gif'
writer = PillowWriter(fps=12)
anim.save(out_gif, writer=writer, dpi=100)
plt.close(fig)
print(f"Saved: {out_gif}")
print("Done.")