How to Integrate ESMC Embeddings with Molecular Dynamics Simulations
You can integrate ESM‑C embeddings with molecular dynamics (MD) simulations by generating per‑residue embeddings with the ESMC model, mapping them to structural coordinates via MolecularComplex, and exporting the enriched data to mmCIF or PDB format for use as restraint weights or collective variables in MD engines like OpenMM or GROMACS.
The Biohub/esm repository provides a complete toolkit for bridging protein language models and classical simulation workflows. By combining the transformer‑based ESM‑C architecture with the structural utilities in esm.utils.structure, you can directly incorporate sequence‑derived embeddings into molecular dynamics pipelines without external alignment tools.
Overview of the Integration Pipeline
The workflow relies on three core components that handle sequence encoding, structure representation, and data export.
Core Components
-
esm.models.esmc.ESMC(located inesm/models/esmc.py): The transformer model that generates per‑residue embeddings. Thelogits()method accepts aLogitsConfigwithreturn_embeddings=Trueto output anESMCOutputobject containing the embedding tensor. -
esm.utils.structure.molecular_complex.MolecularComplex(located inesm/utils/structure/molecular_complex.py): A flat‑atom representation that maintains exact token‑to‑atom mapping. Thesequenceattribute preserves the same token order as the embedding tensor, enabling direct assignment of features to residues. -
esm.utils.structure.protein_complex.ProteinComplex(located inesm/utils/structure/protein_complex.py): Provides the classicatom37view andto_pdb()export method required by traditional MD packages.
Step‑by‑Step Implementation
1. Generate Per‑Residue Embeddings
Load a pretrained ESM‑C model and encode your target sequence. Request embeddings via the LogitsConfig API.
import torch
from esm.models.esmc import ESMC
from esm.sdk.api import LogitsConfig, ESMProtein
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ESMC.from_pretrained("esmc_600m", device=device)
protein = ESMProtein(sequence="MKTLLILAVLLVVLGVAGSS")
protein_tensor = model.encode(protein)
logits_out = model.logits(
protein_tensor,
LogitsConfig(sequence=True, return_embeddings=True)
)
embeddings = logits_out.embeddings # Shape: (L, d_model)
2. Load the Corresponding Structure
Import your PDB or mmCIF file using MolecularComplex.from_mmcif(). The token order in MolecularComplex.sequence exactly matches the embedding order returned by the model.
from esm.utils.structure.molecular_complex import MolecularComplex
mol_complex = MolecularComplex.from_mmcif("data/example.pdb")
assert len(mol_complex.sequence) == embeddings.shape[0] # Alignment verified
3. Attach Embeddings and Export
Store scalar values derived from the embeddings (such as the L2 norm) in the plddt field, which maps to the B‑factor column during export. This allows MD engines to read per‑residue confidence or restraint weights.
import numpy as np
# Calculate per‑residue embedding norm
b_factor = embeddings.norm(dim=1).cpu().numpy()
# Broadcast to all atoms belonging to each token
atom_bf = []
for start, end in mol_complex.token_to_atoms:
atom_bf.extend([b_factor[i]] * (end - start))
mol_complex.plddt = np.array(atom_bf, dtype=np.float32)
# Export to mmCIF for MD engines
mmcif_str = mol_complex.to_mmcif()
with open("complex_with_emb.mmcif", "w") as f:
f.write(mmcif_str)
Alternatively, convert to ProteinComplex for PDB export:
protein_complex = mol_complex.to_protein_complex()
pdb_str = protein_complex.to_pdb()
Practical MD Applications
Using Embeddings as Harmonic Restraints (OpenMM)
You can inject embedding‑derived values into a CustomExternalForce to create position restraints that scale with the sequence embedding magnitude.
from openmm import *
from openmm.app import PDBFile, ForceField
pdb = PDBFile("complex_with_emb.pdb")
forcefield = ForceField('amber14-all.xml')
system = forcefield.createSystem(pdb.topology)
# Create restraint using B‑factor values as force constants
restraint = CustomExternalForce('0.5*k*periodicdistance(x,y,z,x0,y0,z0)^2')
restraint.addPerParticleParameter('k')
restraint.addPerParticleParameter('x0')
restraint.addPerParticleParameter('y0')
restraint.addPerParticleParameter('z0')
# Assume b_factors loaded from the PDB file
for i, atom in enumerate(pdb.topology.atoms()):
k = b_factors[i] * 0.1 # Scale to kJ·mol⁻¹·nm⁻²
pos = pdb.positions[i]
restraint.addParticle(i, [k, pos.x, pos.y, pos.z])
system.addForce(restraint)
Coarse‑Grained Simulations with Per‑Token Coordinates
For residue‑level coarse‑grained MD, compute centroids from the flat atom arrays and write an XYZ file.
import numpy as np
centroids = []
for start, end in mol_complex.token_to_atoms:
token_coords = mol_complex.atom_positions[start:end]
centroids.append(token_coords.mean(axis=0))
centroids = np.stack(centroids)
with open("cg_structure.xyz", "w") as f:
f.write(f"{len(centroids)}\n")
f.write("CG model from ESM‑C embeddings\n")
for i, (x, y, z) in enumerate(centroids):
f.write(f"C{i+1:04d} {x:.3f} {y:.3f} {z:.3f}\n")
Key Source Files Reference
| File | Relevance to MD Integration |
|---|---|
esm/models/esmc.py |
Implements ESMC.from_pretrained() and the logits() API used to extract embeddings. |
esm/utils/structure/molecular_complex.py |
Contains from_mmcif(), to_mmcif(), and the token_to_atoms mapping critical for alignment. |
esm/utils/structure/protein_complex.py |
Provides to_pdb() for exporting to legacy MD formats. |
esm/tokenization/sequence_tokenizer.py |
Supplies EsmSequenceTokenizer for converting sequences to model inputs. |
Summary
- ESM‑C generates per‑residue embeddings via
model.logits(..., LogitsConfig(return_embeddings=True)). MolecularComplexmaintains token‑atom alignment automatically, eliminating the need for manual sequence‑structure alignment.- Store embedding‑derived scalars in the
plddtfield to repurpose the B‑factor column for restraint weights. - Export to mmCIF or PDB using
to_mmcif()orto_pdb()for direct consumption by OpenMM, GROMACS, or AMBER.
Frequently Asked Questions
How do I ensure token‑to‑residue alignment when integrating ESMC embeddings with MD?
The MolecularComplex.sequence attribute uses the exact same tokenization as the ESMC model. When you load a structure with MolecularComplex.from_mmcif(), the resulting object preserves the token order, allowing direct indexing between embeddings[i] and the residue at mol_complex.sequence[i].
Can I use ESMC embeddings directly as force field parameters?
Yes, you can derive scalar values from embeddings—such as the L2 norm, variance, or specific dimension values—and map them to per‑atom force constants or collective variable biases. The Biohub/esm source code demonstrates this by storing derived values in the plddt field, which exports to the B‑factor column readable by most MD engines.
What file format should I use to export structures for GROMACS or AMBER?
Use MolecularComplex.to_mmcif() for GROMACS, as it supports modern mmCIF format with explicit chain IDs. For AMBER or legacy workflows, convert to ProteinComplex via to_protein_complex() and call to_pdb() to generate standard PDB files that preserve the embedding‑derived B‑factors.
Is GPU acceleration required for generating embeddings before MD simulations?
While GPU acceleration significantly speeds up embedding generation via ESMC, it is not strictly required. The model supports CPU inference, and once the embeddings are exported to a structure file, the subsequent MD simulation runs independently on any hardware supported by your chosen MD engine.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →