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

Asking for Preferences
Searching the Web

AlphaFold2 pipeline Evoformer MSA structure module explained research

Running Command

mkdir -p /home/daytona/workspace/alphafold-animation && ls /home/daytona/workspa…

Writing File

~/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.")
Running Command

cd /home/daytona/workspace/alphafold-animation && python alphafold_animation.py …

Writing File

~/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.")
Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.
This is a shared conversation. Sign in to Orris to start your own chat.