Step-by-Step MD Simulation of a Protein–Ligand Complex with GROMACS

Apr 4, 2025 · 6 min read

This is a recorded CHARMM36 recipe, not a universal GROMACS map. The protein uses CHARMM36, the ligand uses CGenFF, and the write-up calls the solvent TIP3P. The commands below are the ones to copy. Menu numbers and index groups belong to the system they were answered on.

em.mdp, nvt.mdp, npt.mdp, and md.mdp are not in this note. The production run is the 100 ns job written as md_0_100. Ions use the short ions.mdp that is included.

CHARMM36 protein-ligand setup through productionNine stages: fix the PDB, build the protein topology, parameterize the ligand with CGenFF, merge, solvate and add ions, minimize, NVT, NPT, then a 100 ns production run named md_0_100.One system, nine stagesFix the PDBpdbfixerProtein topologypdb2gmxLigandCGenFFMergecomplex.groSolvate, ions1.4 nm, 0.15 MMinimizeem.mdpNVT300 KNPTParrinello–Rahman100 nsmd_0_100Index groups are localgenion answered 15. The complex group was 1 | 13.Read the menu on your build before reusing either.
Figure 1. The box is cubic with a 1.4 nm buffer, and the salt command is 0.15 M NaCl. Group 15 at the ion step and 1 | 13 in make_ndx are the answers printed for this system. Solvation uses spc216.gro even though the force field named here is CHARMM36 with TIP3P. That coordinate file is left as written.

0. Repair the PDB

Missing atoms get filled before the force field sees the file.

conda activate pdbfixer
pdbfixer protein.pdb --output=protein_fixed.pdb

1. Separate the ligand

Unknown residues and cofactors should not go through pdb2gmx as part of the protein. Two variants are recorded. One pulls UNK only. The other also drops FAD.

grep UNK protein.pdb > unk.pdb
grep -v "UNK" protein.pdb > clean.pdb

grep -v -e "UNK" -e "FAD" protein_fixed.pdb > clean.pdb

conda activate pdbfixer
pdbfixer clean.pdb --output=clean.pdb

2. Protein topology

pdb2gmx is run with -ter and -ignh. The six lines are the menu answers recorded for this CHARMM36 setup. On another GROMACS build the same digit can be a different force field or a different terminus. Read the menu.

gmx pdb2gmx -f clean.pdb -o processed.gro -ter -ignh << EOF
1
1
0
0
0
0
EOF

3. Ligand MOL2

Hydrogens are added and a 3D guess is generated. The sed line sets the residue name to UNK.

obabel unk.pdb -O unk.mol2 --addh --gen3d
sed -i '2s/.*/UNK/; s/UNK1/UNK/g' unk.mol2

4. Bond order in the MOL2

perl sort_mol2_bonds.pl unk.mol2 unk_fix.mol2

5. CGenFF

The binary path is the one used on this machine. The conversion script targets charmm36-jul2022.ff. Penalty scores in the CGenFF log are a reason to stop. This recipe does not inspect them for you.

~/mdrun/silcsbio.2024.1/cgenff/cgenff unk_fix.mol2 -f unk.str
chmod +x cgenff_charmm2gmx.py

conda activate cgenff
python cgenff_charmm2gmx.py UNK unk_fix.mol2 unk.str charmm36-jul2022.ff

rm cgenff_charmm2gmx.py

6. Ligand coordinates as GRO

The next step expects unk_ini.pdb and, when a cofactor is present, fad_ini.pdb. The command that writes those two names is not in this note. The MOL2 above is the file that went to CGenFF.

gmx editconf -f unk_ini.pdb -o unk.gro
gmx editconf -f fad_ini.pdb -o fad.gro

7. Merge into one GRO

Protein plus ligand. The second line of a GRO file is the atom count, so it is rewritten after the append. The box line is dropped from the protein file and a blank box line is added back at the end.

cp processed.gro complex.gro && sed -i '$d' complex.gro && tail -n +3 unk.gro >> complex.gro && echo "   " >> complex.gro

num1=$(sed -n '2p' complex.gro)
num2=$(sed -n '2p' unk.gro)
sum=$((num1 + num2))
sed -i "2s/.*/ $sum/" complex.gro

Protein plus FAD plus UNK:

cp processed.gro complex.gro && \
sed -i '$d' complex.gro && \
tail -n +3 fad.gro >> complex.gro && \
tail -n +3 unk.gro >> complex.gro && \
echo "   " >> complex.gro

num1=$(sed -n '2p' processed.gro)
num2=$(sed -n '2p' fad.gro)
num3=$(sed -n '2p' unk.gro)
sum=$((num1 + num2 + num3))
sed -i "2s/.*/ $sum/" complex.gro

8. Topology includes

These sed edits are brittle. They assume the CHARMM36 comments and the molecule name Protein_chain_A are still in topol.top. Open the topology after they run.

Ligand only:

sed -i '/; Include Position restraint file/{:a;N;/#endif/!ba;s/\n#endif\n/\n#endif/;s/#endif/#endif\n\n; Include ligand topology\n#include "unk.itp"/}' topol.top

sed -i '/; Include forcefield parameters/{:a;N;/#include ".\/charmm36-jul2022.ff\/forcefield.itp"/!ba;s/#include ".\/charmm36-jul2022.ff\/forcefield.itp"/&\n\n; Include ligand parameters\n#include "unk.prm"/}' topol.top

sed -i '/\[ molecules \]/,/Protein_chain_A/ {/Protein_chain_A/ a\\nUNK                 1\n}' topol.top

FAD and UNK:

sed -i '/; Include Position restraint file/{:a;N;/#endif/!ba;s/#endif/#endif\n\n; Include ligand topologies\n#include "fad.itp"\n#include "unk.itp"/}' topol.top

sed -i '/; Include forcefield parameters/{:a;N;/#include ".\/charmm36-jul2022.ff\/forcefield.itp"/!ba;s|#include ".\/charmm36-jul2022.ff\/forcefield.itp"|&\n\n; Include ligand parameters\n#include "fad.prm"\n#include "unk.prm"|}' topol.top

sed -i '/\[ molecules \]/,/Protein_chain_A/ {/Protein_chain_A/ a\\nFAD                 1\\nUNK                 1\n}' topol.top

9. Box and solvent

The buffer is 1.4 nm and the box is cubic. Solvation is spc216.gro. That is the SPC216 coordinate file, used here with the CHARMM36 topology. It is not rewritten as tip3p.gro.

gmx editconf -f complex.gro -o newbox.gro -bt cubic -c -d 1.4
gmx solvate -cp newbox.gro -cs spc216.gro -p topol.top -o solv.gro

10. Ions

The ion .mdp is only a steep minimizer so genion has a .tpr. Neutralize, then add 0.15 M NaCl. The heredoc answers group 15. On this system that was solvent. genion prints the group list. Choose solvent, whatever number it has.

cat <<EOF > ions.mdp
integrator     = steep
emtol          = 1000.0
nsteps         = 50000
cutoff-scheme  = Verlet
coulombtype    = PME
rcoulomb       = 1.0
rvdw           = 1.0
constraints    = none
EOF

gmx grompp -f ions.mdp -c solv.gro -p topol.top -o ions.tpr -maxwarn 3
gmx genion -s ions.tpr -o solv_ions.gro -p topol.top -pname NA -nname CL -neutral -conc 0.15 <<EOF
15
EOF

11. Energy minimization

em.mdp itself is not listed.

gmx grompp -f em.mdp -c solv_ions.gro -p topol.top -o em.tpr
gmx mdrun -v -deffnm em

12. Ligand position restraints

Non-hydrogen atoms of the ligand are restrained with -fc 1000 1000 1000. Group 3 is the selection recorded after 0 & ! a H*. Check the index make_ndx prints.

gmx make_ndx -f unk.gro -o index_unk.ndx << EOF
0 & ! a H*
q
EOF

gmx genrestr -f unk.gro -n index_unk.ndx -o posre_unk.itp -fc 1000 1000 1000 <<EOF
3
EOF

sed -i '/; Include water topology/{x;s/.*/\n; Ligand position restraints\n#ifdef POSRES\n#include "posre_unk.itp"\n#endif\n/;G}' topol.top

13. Complex index

1 | 13 unions two groups that were protein and ligand on this system. Use the numbers your own index file prints.

gmx make_ndx -f solv_ions.gro -o index.ndx << EOF
1 | 13
q
EOF

14. NVT

Temperature equilibration at 300 K, with position restraints. The thermostat settings live in nvt.mdp, which is not pasted here.

gmx grompp -f nvt.mdp -c solv_ions.gro -r solv_ions.gro -p topol.top -n index.ndx -o nvt.tpr
gmx mdrun -deffnm nvt

15. NPT

Pressure equilibration at 1 bar with the Parrinello–Rahman barostat.

gmx grompp -f npt.mdp -c nvt.gro -t nvt.cpt -r nvt.gro -p topol.top -n index.ndx -o npt.tpr
gmx mdrun -deffnm npt

16. Production

The file names are the 100 ns run.

gmx grompp -f md.mdp -c npt.gro -t npt.cpt -p topol.top -n index.ndx -o md_0_100.tpr
gmx mdrun -deffnm md_0_100

Before you plot anything

Fix periodic boundary crossings before RMSD, radius of gyration, or SASA. Drop the first 5–10 ns. A flat RMSD, radius of gyration, and SASA is the stability check this note uses. PCA and a free-energy landscape are the next look at slow motions. Those analysis commands are a separate post.