🔬 The NeRF Geometry Lab¶
Interactive Exploration of the "Hidden Math" Behind Protein AI¶
🎯 What You'll Learn¶
Most modern protein AI models (like AlphaFold and trRosetta) don't predict 3D coordinates directly. Instead, they predict internal coordinates (bond lengths, angles, and torsions) and then use an algorithm called NeRF (Natural Extension Reference Frame) to build the 3D model atom-by-atom.
In this tutorial:
- 📐 Explore the Z-Matrix (internal coordinate representation)
- 🎛️ Use interactive sliders to manipulate backbone torsions (φ and ψ)
- 📊 Watch a real-time Ramachandran plot update as you change angles
- 🗺️ Visualize distance matrices showing how local changes affect global structure
💡 Why This Matters: Understanding internal coordinates is crucial for protein structure prediction, molecular dynamics, and protein design. This is the mathematical foundation of modern structural biology AI.
# 🔧 Environment Detection & Setup
import os
import sys
IN_COLAB = 'google.colab' in sys.modules
if IN_COLAB:
print('🌐 Running in Google Colab')
try:
import synth_pdb
print(' ✅ synth-pdb already installed')
except ImportError:
print(' 📦 Installing synth-pdb and dependencies...')
!pip install -q synth-pdb py3Dmol biotite
print(' ✅ Installation complete')
import plotly.io as pio
pio.renderers.default = 'colab'
else:
print('💻 Running in local Jupyter environment')
sys.path.append(os.path.abspath('../../'))
print('✅ Environment configured!')
import io
import ipywidgets as widgets
import numpy as np
import plotly.graph_objects as go
import py3Dmol
from biotite.structure.io.pdb import PDBFile
from IPython.display import HTML, clear_output, display
from plotly.subplots import make_subplots
from synth_pdb import PeptideGenerator
gen = PeptideGenerator('ALA-ALA-ALA-ALA-ALA-ALA-ALA-ALA-ALA-ALA')
print('✅ NeRF Geometry Lab Ready!')
print(' Loaded: 10-residue polyalanine α-helix')
📚 Internal Coordinates: The Language of Protein Structure¶
Z-Matrix Representation¶
Instead of Cartesian coordinates (x, y, z), proteins can be described using internal coordinates:
| Coordinate | Symbol | Description | Typical Range |
|---|---|---|---|
| Bond Length | r | Distance between bonded atoms | 1.0-1.5 Å |
| Bond Angle | θ | Angle between 3 consecutive atoms | 100-120° |
| Dihedral Angle | φ, ψ, ω | Rotation around bonds | -180° to +180° |
The Backbone Dihedrals¶
For each residue i, we have three key angles:
$$\phi_i = \text{dihedral}(C_{i-1}, N_i, C\alpha_i, C_i)$$$$\psi_i = \text{dihedral}(N_i, C\alpha_i, C_i, N_{i+1})$$$$\omega_i = \text{dihedral}(C\alpha_i, C_i, N_{i+1}, C\alpha_{i+1})$$- φ (phi): Rotation around N-Cα bond
- ψ (psi): Rotation around Cα-C bond
- ω (omega): Peptide bond rotation (usually ~180° for trans, ~0° for cis)
🔬 NeRF Algorithm: Given these angles, NeRF reconstructs 3D coordinates by:
- Placing the first 3 atoms arbitrarily
- For each new atom: use bond length, angle, and dihedral to calculate position
- Build the entire structure atom-by-atom in a single forward pass
🎛️ Interactive Geometry Lab¶
Use the sliders below to modify the φ and ψ angles of the central residue (residue 5). Watch how:
- The 3D structure changes in real-time
- The Ramachandran plot shows your current position
- The distance matrix reveals how local changes affect global structure
Try these experiments:
- Move to the β-sheet region: φ ≈ -120°, ψ ≈ +120°
- Explore forbidden regions and see steric clashes
- Create a β-turn by setting φ ≈ -60°, ψ ≈ -30°
⚠️ Important: If you see duplicate visualizations, restart your kernel (Kernel → Restart Kernel) and run all cells from the top.
import os
IN_CI = bool(os.getenv("CI"))
if not IN_CI:
# Output area
out = widgets.Output()
# Sliders with enhanced styling
phi_slider = widgets.FloatSlider(
min=-180, max=180, step=10, value=0,
description='Δφ:',
continuous_update=False,
style={'description_width': '50px'},
layout=widgets.Layout(width='500px')
)
psi_slider = widgets.FloatSlider(
min=-180, max=180, step=10, value=0,
description='Δψ:',
continuous_update=False,
style={'description_width': '50px'},
layout=widgets.Layout(width='500px')
)
# Track initialization
_initializing = True
def update(change=None):
global _initializing
if _initializing and change is not None:
return
phi, psi = phi_slider.value, psi_slider.value
phis, psis = [-57.0]*10, [-47.0]*10
phis[4] += phi
psis[4] += psi
res = gen.generate(phi_list=phis, psi_list=psis)
final_phi = -57.0 + phi
final_psi = -47.0 + psi
# Determine region
region = 'Unknown'
region_color = '#FFD700'
if -90 < final_phi < -30 and -70 < final_psi < -20:
region = 'α-helix ✓'
region_color = '#00FF00'
elif -150 < final_phi < -90 and 90 < final_psi < 150:
region = 'β-sheet ✓'
region_color = '#87CEEB'
elif -90 < final_phi < -60 and 120 < final_psi < 170:
region = 'PPII ✓'
region_color = '#90EE90'
else:
region = 'Non-canonical ⚠️'
region_color = '#FF6B6B'
with out:
clear_output(wait=True)
# Info panel
display(HTML(f"""
<div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; padding: 15px; border-radius: 10px;
font-family: monospace; box-shadow: 0 4px 6px rgba(0,0,0,0.3);
margin-bottom: 15px;'>
<b>🎯 Current Angles:</b><br>
φ = {final_phi:.1f}° | ψ = {final_psi:.1f}°<br>
<b>Region:</b> <span style='color: {region_color};'>{region}</span>
</div>
"""))
# 3D structure
pf = PDBFile()
pf.set_structure(res.structure)
s = io.StringIO()
pf.write(s)
v = py3Dmol.view(width=700, height=400)
v.addModel(s.getvalue(), 'pdb')
v.setStyle({'stick': {'colorscheme': 'chainHetatm', 'radius': 0.15}})
v.setStyle({'resi': 5}, {'stick': {'color': 'red', 'radius': 0.25}})
v.setBackgroundColor('#1a1a1a')
v.zoomTo()
display(v.show())
# Plots
fig = make_subplots(
rows=1, cols=2,
subplot_titles=('Ramachandran Plot', 'Cα Distance Matrix')
)
# Ramachandran with regions
regions = [
{'type': 'rect', 'x0': -90, 'x1': -30, 'y0': -70, 'y1': -20,
'fillcolor': 'rgba(0,100,200,0.2)', 'line': {'width': 0}},
{'type': 'rect', 'x0': -150, 'x1': -90, 'y0': 90, 'y1': 150,
'fillcolor': 'rgba(200,100,0,0.2)', 'line': {'width': 0}},
{'type': 'rect', 'x0': -90, 'x1': -60, 'y0': 120, 'y1': 170,
'fillcolor': 'rgba(100,200,0,0.2)', 'line': {'width': 0}}
]
for r in regions:
fig.add_shape(r, row=1, col=1)
fig.add_trace(go.Scatter(
x=[final_phi], y=[final_psi], mode='markers',
marker={'size': 15, 'color': 'red', 'symbol': 'star',
'line': {'color': 'white', 'width': 2}},
hovertemplate='φ: %{x:.1f}°<br>ψ: %{y:.1f}°<extra></extra>'
), row=1, col=1)
fig.update_xaxes(title_text='Phi φ (degrees)', range=[-180,180], dtick=60, row=1, col=1)
fig.update_yaxes(title_text='Psi ψ (degrees)', range=[-180,180], dtick=60, row=1, col=1)
# Distance matrix
ca = res.structure[res.structure.atom_name=='CA']
n = len(ca)
dm = np.zeros((n,n))
for i in range(n):
for j in range(n):
dm[i,j] = np.linalg.norm(ca.coord[i] - ca.coord[j])
fig.add_trace(go.Heatmap(
z=dm, colorscale='Viridis',
colorbar={'title': 'Distance (Å)'},
hovertemplate='Residue %{x} ↔ %{y}<br>Distance: %{z:.1f} Å<extra></extra>'
), row=1, col=2)
fig.update_xaxes(title_text='Residue', dtick=1, row=1, col=2)
fig.update_yaxes(title_text='Residue', dtick=1, row=1, col=2)
fig.update_layout(
height=400, width=900,
template='plotly_dark',
showlegend=False
)
display(fig)
# Connect sliders
phi_slider.observe(update, 'value')
psi_slider.observe(update, 'value')
# Display UI
display(widgets.VBox([phi_slider, psi_slider, out]))
# Initialize
_initializing = False
# 3Dmol.js initializes asynchronously in the browser. Calling update()
# at kernel execution time races with the library bootstrap and produces
# the 'failed to load' error. Show a placeholder instead; the first
# slider change fires update() after 3Dmol.js is fully ready.
with out:
from IPython.display import HTML as _HTML
from IPython.display import display as _disp
_disp(_HTML(
'<div style="text-align:center;padding:40px;color:#aaa;'
'border:1px dashed #555;border-radius:8px;'
'font-style:italic;background:#1a1a1a;">'
'🔄 Move a slider above to load the 3D structure'
'</div>'
))
# (Widget output skipped in CI)
φ = {final_phi:.1f}° | ψ = {final_psi:.1f}°
Region: {region}
ψ: %{y:.1f}°
Distance: %{z:.1f} Å
🎓 Key Insights¶
- Local Changes → Global Effects: Changing one residue's angles affects the entire downstream structure
- Ramachandran Constraints: Only certain φ/ψ combinations are sterically allowed
- Distance Patterns: α-helices show characteristic i, i+4 contacts; β-sheets show long-range contacts
- NeRF Reconstruction: This is exactly how AlphaFold and other AI models build 3D structures!
📖 Further Reading¶
- Jumper et al. (2021). "Highly accurate protein structure prediction with AlphaFold." Nature 596:583-589. DOI: 10.1038/s41586-021-03819-2
- Parsons et al. (2005). "Practical conversion from torsion space to Cartesian space for in silico protein synthesis." J Comput Chem 26:1063-1068. DOI: 10.1002/jcc.20237
- Ramachandran et al. (1963). "Stereochemistry of polypeptide chain configurations." J Mol Biol 7:95-99. DOI: 10.1016/S0022-2836(63)80023-6
🎉 Lab Session Complete!
You've mastered internal coordinates and NeRF geometry!