Spaces:
Sleeping
Sleeping
| """AlphaDynamics interactive demo on Hugging Face Spaces. | |
| User pastes a peptide sequence, gets: | |
| - interactive Ramachandran density plot (Plotly) | |
| - per-residue panels for short peptides | |
| - basin populations table (alpha-R, beta, PPII, alpha-L) | |
| - downloadable .npz with the raw trajectory tensor | |
| Free CPU tier. Limits: max 50 residues, max 16 trajectories, max 1000 steps. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import tempfile | |
| from pathlib import Path | |
| import gradio as gr | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| # alphadynamics is in requirements.txt β installed at Space build time | |
| from alphadynamics import predict_torsion_ensemble | |
| # ---------------------------------------------------------------------------- | |
| # Constants β protect free CPU tier | |
| # ---------------------------------------------------------------------------- | |
| MAX_RESIDUES = 50 | |
| MAX_ENSEMBLE = 16 | |
| MAX_STEPS = 1000 | |
| EXAMPLES = [ | |
| ["AAAY", 8, 500], | |
| ["KLVFFAE", 8, 500], # amyloid-Ξ² fragment | |
| ["GNNQQNY", 8, 500], # Sup35 prion peptide | |
| ["AAYAA", 4, 300], # quick test | |
| ["MAEHLLLY", 8, 500], # random short | |
| ["FVNQHLCGSHLVEALYL", 4, 300], # insulin B chain N-terminus | |
| ] | |
| # ---------------------------------------------------------------------------- | |
| # Plotting | |
| # ---------------------------------------------------------------------------- | |
| def make_ramachandran_figure(traj: np.ndarray, sequence: str) -> go.Figure: | |
| n_ens, n_t, n_res, _ = traj.shape | |
| has_per_res = 1 <= n_res <= 12 | |
| if has_per_res: | |
| ncols = min(4, n_res) | |
| nrows_per_res = (n_res + ncols - 1) // ncols | |
| specs = [[{"colspan": ncols, "rowspan": 1}] + [None] * (ncols - 1)] | |
| for _ in range(nrows_per_res): | |
| specs.append([{} for _ in range(ncols)]) | |
| titles = [f"<b>{sequence}</b> β aggregate density"] | |
| for i in range(n_res): | |
| aa = sequence[i] if i < len(sequence) else "?" | |
| titles.append(f"{aa}{i+1}") | |
| # pad | |
| total_cells = ncols + nrows_per_res * ncols | |
| titles += [""] * max(0, total_cells - len(titles)) | |
| fig = make_subplots( | |
| rows=1 + nrows_per_res, cols=ncols, specs=specs, | |
| subplot_titles=titles, | |
| horizontal_spacing=0.04, vertical_spacing=0.10, | |
| ) | |
| else: | |
| fig = make_subplots( | |
| rows=1, cols=1, | |
| subplot_titles=[ | |
| f"<b>{sequence}</b> β Ramachandran ({n_res} residues, {n_ens}Γ{n_t} samples)" | |
| ], | |
| ) | |
| ncols = 1 | |
| nrows_per_res = 0 | |
| phi_all = np.degrees(traj[..., 0].flatten()) | |
| psi_all = np.degrees(traj[..., 1].flatten()) | |
| fig.add_trace( | |
| go.Histogram2dContour( | |
| x=phi_all, y=psi_all, | |
| colorscale="Viridis", | |
| ncontours=20, | |
| contours=dict(coloring="fill", showlines=False), | |
| line=dict(width=0), | |
| showscale=True, | |
| colorbar=dict(title="density", | |
| len=0.45 if has_per_res else 0.85, | |
| y=0.78 if has_per_res else 0.5, | |
| x=1.02), | |
| hovertemplate="Ο=%{x:.0f}Β°<br>Ο=%{y:.0f}Β°<br>density=%{z:.4f}<extra></extra>", | |
| ), | |
| row=1, col=1, | |
| ) | |
| for px, py, label, color in [ | |
| (-60, -45, "Ξ±-R", "white"), | |
| (-120, 130, "Ξ²", "white"), | |
| (-60, 140, "PPII", "white"), | |
| (60, 50, "Ξ±-L (forbidden)", "orange"), | |
| ]: | |
| fig.add_annotation( | |
| x=px, y=py, text=f"<b>{label}</b>", | |
| showarrow=False, font=dict(color=color, size=12), | |
| xref="x1", yref="y1", | |
| ) | |
| if has_per_res: | |
| for i in range(n_res): | |
| row = 2 + (i // ncols) | |
| col = 1 + (i % ncols) | |
| phi_i = np.degrees(traj[:, :, i, 0].flatten()) | |
| psi_i = np.degrees(traj[:, :, i, 1].flatten()) | |
| fig.add_trace( | |
| go.Histogram2dContour( | |
| x=phi_i, y=psi_i, | |
| colorscale="Viridis", | |
| ncontours=15, | |
| contours=dict(coloring="fill", showlines=False), | |
| line=dict(width=0), | |
| showscale=False, | |
| hovertemplate="Ο=%{x:.0f}Β°<br>Ο=%{y:.0f}Β°<extra></extra>", | |
| ), | |
| row=row, col=col, | |
| ) | |
| for axis in fig.layout: | |
| if axis.startswith("xaxis"): | |
| fig.layout[axis].update( | |
| range=[-180, 180], tickvals=[-180, -90, 0, 90, 180], | |
| ticksuffix="Β°", gridcolor="rgba(255,255,255,0.15)", | |
| zerolinecolor="rgba(255,255,255,0.4)", zerolinewidth=1, | |
| ) | |
| elif axis.startswith("yaxis"): | |
| suffix = axis[len("yaxis"):] | |
| fig.layout[axis].update( | |
| range=[-180, 180], tickvals=[-180, -90, 0, 90, 180], | |
| ticksuffix="Β°", gridcolor="rgba(255,255,255,0.15)", | |
| zerolinecolor="rgba(255,255,255,0.4)", zerolinewidth=1, | |
| scaleanchor="x" + suffix, scaleratio=1, | |
| ) | |
| fig.update_layout( | |
| template="plotly_dark", | |
| height=350 + (250 * nrows_per_res if has_per_res else 0), | |
| margin=dict(l=70, r=120, t=80, b=60), | |
| font=dict(family="Inter, system-ui, -apple-system, sans-serif", size=12), | |
| hovermode="closest", | |
| ) | |
| return fig | |
| def basin_table_md(traj: np.ndarray) -> str: | |
| phi = np.degrees(traj[..., 0].flatten()) | |
| psi = np.degrees(traj[..., 1].flatten()) | |
| def b(plo, phi_, slo, shi): | |
| return ((phi >= plo) & (phi <= phi_) & (psi >= slo) & (psi <= shi)).mean() * 100 | |
| return ( | |
| "| Basin | Population |\n" | |
| "|---|---:|\n" | |
| f"| Ξ±-R helix (Ο β -60, Ο β -45) | **{b(-130,-30,-90,30):.1f}%** |\n" | |
| f"| Ξ²-sheet (Ο β -120, Ο β 120) | **{b(-180,-90,70,180):.1f}%** |\n" | |
| f"| PPII extended (Ο β -60, Ο β 140) | **{b(-90,-30,100,180):.1f}%** |\n" | |
| f"| Ξ±-L (sterically forbidden region) | **{b(30,100,-10,90):.1f}%** β should be near 0 |\n" | |
| ) | |
| # ---------------------------------------------------------------------------- | |
| # Inference | |
| # ---------------------------------------------------------------------------- | |
| _AA_VOCAB = set("ACDEFGHIKLMNPQRSTVWYX") | |
| def predict(sequence: str, n_ensemble: int, rollout_steps: int): | |
| sequence = (sequence or "").strip().upper() | |
| if not sequence: | |
| raise gr.Error("Please enter a peptide sequence (1-letter amino-acid code).") | |
| if len(sequence) > MAX_RESIDUES: | |
| raise gr.Error( | |
| f"Free demo limited to {MAX_RESIDUES} residues. " | |
| f"For longer peptides, install locally: pip install alphadynamics" | |
| ) | |
| bad = sorted(set(sequence) - _AA_VOCAB) | |
| if bad: | |
| raise gr.Error( | |
| f"Sequence contains non-standard residues: {bad}. " | |
| f"Use only one-letter codes from {sorted(_AA_VOCAB)}." | |
| ) | |
| n_ensemble = max(1, min(int(n_ensemble), MAX_ENSEMBLE)) | |
| rollout_steps = max(50, min(int(rollout_steps), MAX_STEPS)) | |
| traj = predict_torsion_ensemble( | |
| sequence, | |
| n_ensemble=n_ensemble, | |
| rollout_steps=rollout_steps, | |
| seed=42, | |
| device="cpu", | |
| show_progress=False, | |
| ) | |
| # save NPZ for download | |
| out_path = Path(tempfile.gettempdir()) / f"alphadynamics_{sequence}_torsions.npz" | |
| np.savez_compressed( | |
| out_path, | |
| sequence=sequence, | |
| torsions=traj, | |
| torsion_units="radians", | |
| torsion_axes="(ensemble, time, residues, [phi, psi])", | |
| n_ensemble=n_ensemble, | |
| rollout_steps=rollout_steps, | |
| model_name="ad_transfer_v2_clean", | |
| ) | |
| # Generate backbone PDB (NEW v0.4.0) | |
| try: | |
| from alphadynamics import trajectory_to_pdb, trajectory_diagnostics | |
| pdb_path = Path(tempfile.gettempdir()) / f"alphadynamics_{sequence}_backbone.pdb" | |
| # Use ensemble member 0, subsample to 50 frames for fast download | |
| member = traj[0] | |
| if len(member) > 50: | |
| idx = np.linspace(0, len(member) - 1, 50).astype(int) | |
| member = member[idx] | |
| trajectory_to_pdb(member, sequence, str(pdb_path)) | |
| diag = trajectory_diagnostics(member) | |
| diag_md = ( | |
| f"\n\n**3D backbone diagnostics** (CΞ±-only, 50 frames):\n" | |
| f"- Radius of gyration: {diag['rg_mean']:.2f} Β± {diag['rg_std']:.2f} Γ \n" | |
| f"- End-to-end distance: {diag['end_to_end_mean']:.2f} Β± {diag['end_to_end_std']:.2f} Γ " | |
| ) | |
| except Exception as e: | |
| pdb_path = None | |
| diag_md = f"\n\n*(PDB rebuild unavailable: {e})*" | |
| fig = make_ramachandran_figure(traj, sequence) | |
| info = ( | |
| f"### `{sequence}` β {len(sequence)} residue{'s' if len(sequence) > 1 else ''}\n\n" | |
| f"**{n_ensemble}** trajectories Γ **{rollout_steps}** steps " | |
| f"= **{n_ensemble * rollout_steps * len(sequence):,}** torsion samples\n\n" | |
| + basin_table_md(traj) | |
| + diag_md | |
| ) | |
| return fig, info, str(out_path), str(pdb_path) if pdb_path else None | |
| # ---------------------------------------------------------------------------- | |
| # Gradio UI | |
| # ---------------------------------------------------------------------------- | |
| DESCRIPTION = """ | |
| # 𧬠AlphaDynamics β Protein Torsion Dynamics from Sequence | |
| A tiny (~123K param) neural propagator that predicts the **Ramachandran | |
| density** of a peptide's backbone (Ο, Ο) angles **from sequence alone**. | |
| On the canonical 4AA benchmark: **2.39Γ lower JSD** than Microsoft Timewarp at | |
| **3000Γ fewer parameters**. Cross-validated against Top8000 PDB statistics. | |
| π¦ `pip install alphadynamics` Β· π [GitHub](https://github.com/krisss0mecom/AlphaDynamics) | |
| Β· π€ [Model card](https://huggingface.co/krissss0/alphadynamics) | |
| **This demo is CPU-only** β limited to 50 residues / 16 trajectories / 1000 | |
| steps. For longer peptides or larger ensembles, install locally. | |
| """ | |
| NOTES = """ | |
| ### What this tool does | |
| Predicts an ensemble of (Ο, Ο) torsion-angle trajectories for any peptide | |
| sequence (4β100 residues recommended, capped at 50 on this demo). Useful for: | |
| - **Quick conformational triage** before launching expensive MD simulations | |
| - **Comparing sequence variants / mutants** side by side | |
| - **Estimating Ξ±-helix / Ξ²-sheet / PPII basin populations** | |
| - **Teaching biochemistry** with live, interactive Ramachandran plots | |
| - **AI-for-biology baselines and benchmarks** | |
| ### Honest limits | |
| - Density only, **not** kinetics (no transition rates / dwell times) | |
| - Backbone only, **no** side-chain rotamers (Ο angles) | |
| - Monomer only, **no** multimer / aggregation | |
| - Best for 4β100 residue peptides; reliability degrades outside | |
| ### How to read the Ramachandran plot | |
| - **Ξ±-R region** (Ο β -60, Ο β -45) β right-handed alpha-helix | |
| - **Ξ²-sheet region** (Ο β -120, Ο β 120) β extended / beta-strand conformations | |
| - **PPII region** (Ο β -60, Ο β 140) β polyproline-II extended (very common in | |
| short peptides in solution) | |
| - **Ξ±-L region** (Ο β 60, Ο β 50) β left-handed helix, sterically forbidden | |
| for almost all amino acids; should be close to 0% if the model honors physics | |
| ### Architecture (one paragraph) | |
| A residue's (Ο, Ο) is treated as a phase pair on a torus. An MLP emits per-residue | |
| oscillator parameters from sequence + position + current angles. A phase-flow ODE | |
| integrates 64 coupled phase oscillators with RK4. The result is decoded into a | |
| mixture of axis-independent von Mises distributions, sampled, and rolled out | |
| autoregressively. | |
| This is the protein-dynamics application of a multi-year line of work on phase | |
| oscillators (REZON hardware, phase-entanglement-rc, theta-gamma neural coupling). | |
| """ | |
| with gr.Blocks(title="AlphaDynamics") as demo: | |
| gr.Markdown(DESCRIPTION) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| seq_input = gr.Textbox( | |
| label="Peptide sequence", | |
| placeholder="e.g. KLVFFAE", | |
| value="AAAY", | |
| max_lines=1, | |
| ) | |
| with gr.Row(): | |
| n_ens_input = gr.Slider( | |
| minimum=1, maximum=MAX_ENSEMBLE, step=1, value=8, | |
| label="Trajectories (more = smoother density)", | |
| ) | |
| rs_input = gr.Slider( | |
| minimum=50, maximum=MAX_STEPS, step=50, value=500, | |
| label="Steps per trajectory", | |
| ) | |
| predict_btn = gr.Button("Predict torsion dynamics π", variant="primary") | |
| info_md = gr.Markdown(label="Basin populations") | |
| file_out = gr.File(label="Download trajectory (.npz)") | |
| pdb_out = gr.File(label="Download backbone PDB (NEW v0.4.0)") | |
| gr.Markdown( | |
| "π‘ **3D viewer:** open the downloaded `.pdb` in " | |
| "[PyMOL](https://pymol.org/) / VMD / ChimeraX, or browse online at " | |
| "[krisss0mecom.github.io/AlphaDynamics/examples/3d_movie_demo](https://krisss0mecom.github.io/AlphaDynamics/examples/3d_movie_demo/viewer.html)" | |
| ) | |
| with gr.Column(scale=2): | |
| plot_out = gr.Plot(label="Ramachandran density") | |
| predict_btn.click( | |
| fn=predict, | |
| inputs=[seq_input, n_ens_input, rs_input], | |
| outputs=[plot_out, info_md, file_out, pdb_out], | |
| ) | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=[seq_input, n_ens_input, rs_input], | |
| label="Try a known peptide:", | |
| ) | |
| with gr.Accordion("π About this tool / how to read the plot", open=False): | |
| gr.Markdown(NOTES) | |
| gr.Markdown( | |
| "---\n" | |
| "Created by **Krzysztof Gwozdz** π΅π± Β· Apache 2.0 Β· " | |
| "[Cite](https://huggingface.co/krissss0/alphadynamics) Β· " | |
| "[GitHub Issues](https://github.com/krisss0mecom/AlphaDynamics/issues) for feedback" | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=10).launch(server_name="0.0.0.0") | |