Basic NMR Prediction Tutorial¶
Welcome to synth-nmr! This notebook demonstrates the core functionality of the package: parsing a 3D structural model and predicting its corresponding NMR experimental observables.
This tutorial is designed to run completely inside Google Colab (or any Jupyter environment).
1. Installation & Environment Setup¶
First, we'll install synth-nmr and its structural biology dependency, biotite. We'll also install torch to enable the high-accuracy Neural Network chemical shift predictor.
import sys
if "google.colab" in sys.modules:
!pip install -q synth-nmr biotite torch matplotlib
else:
sys.path.append("../../")
import biotite.structure.io as strucio
import matplotlib.pyplot as plt
import numpy as np
2. Download a Test Structure¶
NMR observables heavily depend on the exact positions of Hydrogen atoms. Therefore, your input PDB must contain hydrogen coordinates.
For this tutorial, we will directly download Ubiquitin (1D3Z). This is a classic NMR structure ensemble, which means it naturally contains all expertly assigned hydrogens.
!wget -q https://files.rcsb.org/download/1D3Z.pdb -O ubiquitin.pdb
3. Load the Structure into Biotite¶
We use biotite to load the PDB file and extract the first model from the ensemble.
# Load the multi-model NMR ensemble
ensemble = strucio.load_structure("ubiquitin.pdb")
# Extract only the first model for our calculations
structure = ensemble[0]
print(f"Loaded {len(structure)} atoms for Ubiquitin Model 1.")
print(f"Contains Hydrogens? {'H' in structure.element}")
4. Predict Chemical Shifts (Neural Predictor)¶
synth-nmr features a state-of-the-art Neural Network predictor that evaluates 3D coordinates, torsion angles, distances, and Coulomb matrices to output highly accurate Chemical Shifts.
Under the hood, if torch isn't available, it will transparently fall back to an empirical SPARTA+ algorithm!
from synth_nmr import predict_chemical_shifts
shifts = predict_chemical_shifts(structure)
# Let's inspect the predicted C-alpha (CA) and Amide (N) shifts for Residue 10 (Glycine)
print("Predicted Chemical Shifts for Residue 10 (Glycine):")
print(f" CA: {shifts['A'][10]['CA']} ppm")
print(f" N: {shifts['A'][10]['N']} ppm")
5. Extract Synthetic NOE Distance Restraints¶
The Nuclear Overhauser Effect (NOE) is proportional to $r^{-6}$ (where r is the distance between two protons). We can extract all pairs of protons that are within a realistic NOE generation radius (typically < 5.0 Å).
from synth_nmr import calculate_synthetic_noes
# Exclude intra-residue connections since they aren't useful for 3D folding
noes = calculate_synthetic_noes(structure, cutoff=5.0, exclude_intra_residue=True)
print(f"Found {len(noes)} inter-residue NOE distance restraints!")
print("\nFirst 3 NOEs:")
for _i, noe in enumerate(noes[:3]):
print(
f" {noe['seq_1']} {noe['atom_name_1']} <--> {noe['seq_2']} {noe['atom_name_2']}: {noe['distance']:.2f} Å"
)
6. Calculate J-Couplings¶
Scalar J-couplings (specifically $^3J_{HN-H\alpha}$) correlate strongly with the backbone $\phi$ dihedral angle via the Karplus equation.
from synth_nmr import calculate_hn_ha_coupling
j_couplings = calculate_hn_ha_coupling(structure)
print("Predicted 3J(HN-HA) Couplings:")
for res_id in [10, 11, 12]:
print(f" Residue {res_id}: {j_couplings['A'][res_id]:.2f} Hz")
# Add py3Dmol for visualization
import sys
IN_COLAB = "google.colab" in sys.modules
if IN_COLAB:
!pip install -q py3Dmol
7. 3D Protein Visualization Colored by Chemical Shift¶
Finally, let's visualize the 3D protein structure. We will color the C-alpha atoms based on their predicted chemical shifts to see how local structure correlates with the NMR observables.
(If running in a static environment where py3Dmol is unsupported, a static matplotlib 3D trace will be generated instead).
# Extract coordinates and chemical shifts for C-alpha atoms
ca_coords = []
ca_shifts = []
chain_a = shifts.get("A", {})
for res_id in sorted(chain_a.keys()):
if "CA" in chain_a[res_id]:
# We need to find the CA atom in the structure
res_atoms = structure[(structure.res_id == res_id) & (structure.atom_name == "CA")]
if len(res_atoms) > 0:
ca_coords.append(res_atoms.coord[0])
ca_shifts.append(chain_a[res_id]["CA"])
ca_coords = np.array(ca_coords)
ca_shifts = np.array(ca_shifts)
try:
import py3Dmol
# Check if we are running interactively
interactive = False
if "ipykernel" in sys.modules:
interactive = True
if IN_COLAB:
interactive = True
if interactive:
view = py3Dmol.view(width=800, height=600)
with open("ubiquitin.pdb") as f:
pdb_data = f.read()
view.addModel(pdb_data, "pdb")
view.setStyle({"cartoon": {"color": "spectrum"}})
view.zoomTo()
print("Interactive py3Dmol Viewer:")
view.show()
else:
raise ImportError("Static environment")
except (ImportError, Exception):
print("py3Dmol not available or running statically. Falling back to matplotlib 3D.")
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
# Plot C-alpha trace
ax.plot(ca_coords[:, 0], ca_coords[:, 1], ca_coords[:, 2], color="gray", alpha=0.5, linewidth=1)
# Scatter colored by chemical shift
sc = ax.scatter(
ca_coords[:, 0],
ca_coords[:, 1],
ca_coords[:, 2],
c=ca_shifts,
cmap="viridis",
s=50,
depthshade=True,
)
plt.colorbar(sc, label="Predicted CA Chemical Shift (ppm)")
ax.set_title("Ubiquitin (1D3Z) C-alpha trace")
ax.set_xlabel("X (Å)")
ax.set_ylabel("Y (Å)")
ax.set_zlabel("Z (Å)")
plt.show()
Next Steps¶
You've successfully simulated a basic set of NMR observables for a protein! You can also explore Residual Dipolar Couplings (RDCs), Relaxation (R1, R2, S2), and NEF export formatting in the package.
Check out the other tutorials on the synth-nmr documentation site.