Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Simulating Ethane

!pip install -q condacolab
import condacolab
condacolab.install()
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 2
      1 get_ipython().system('pip install -q condacolab')
----> 2 import condacolab
      3 condacolab.install()

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/condacolab.py:22
     20 from typing import Dict, AnyStr
     21 from urllib.request import urlopen
---> 22 from distutils.spawn import find_executable
     24 from IPython import get_ipython
     26 try:

ModuleNotFoundError: No module named 'distutils'
%%capture
!conda install -c conda-forge openmm mdtraj parmed
!pip install py3dmol 
import parmed
import mdtraj as md
import py3Dmol
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


import openmm as mm
from   openmm import app
from   openmm.unit import * 

Structure + topology file

ATOM      1  C1  ETH     1      -3.553   2.382   0.000  1.00  0.00           C  
ATOM      2  H11 ETH     1      -3.940   1.922   0.912  1.00  0.00           H  
ATOM      3  H12 ETH     1      -3.941   1.831  -0.859  1.00  0.00           H  
ATOM      4  H13 ETH     1      -3.919   3.410  -0.053  1.00  0.00           H  
ATOM      5  C2  ETH     1      -2.016   2.361   0.000  1.00  0.00           C  
ATOM      6  H21 ETH     1      -1.649   1.333   0.053  1.00  0.00           H  
ATOM      7  H22 ETH     1      -1.627   2.912   0.859  1.00  0.00           H  
ATOM      8  H23 ETH     1      -1.629   2.821  -0.912  1.00  0.00           H  
CONECT    1    3    4    5    2                                       
CONECT    2    1                                                      
CONECT    3    1                                                      
CONECT    4    1                                                      
CONECT    5    8    1    6    7                                       
CONECT    6    5                                                      
CONECT    7    5                                                      
CONECT    8    5                                                      
MASTER        0    0    0    0    0    0    0    0    8    0    8    0
END

Forcefield file

<ForceField>
 <AtomTypes>
  <Type name="0" class="c3" element="C" mass="12.01078"/>
  <Type name="1" class="hc" element="H" mass="1.007947"/>
 </AtomTypes>
 <Residues>
  <Residue name="ETH">
   <Atom name="C1" type="0"/>
   <Atom name="H11" type="1"/>
   <Atom name="H12" type="1"/>
   <Atom name="H13" type="1"/>
   <Atom name="C2" type="0"/>
   <Atom name="H21" type="1"/>
   <Atom name="H22" type="1"/>
   <Atom name="H23" type="1"/>
   <Bond atomName1="C1" atomName2="H11"/>
   <Bond atomName1="C1" atomName2="H12"/>
   <Bond atomName1="C1" atomName2="H13"/>
   <Bond atomName1="C1" atomName2="C2"/>
   <Bond atomName1="C2" atomName2="H21"/>
   <Bond atomName1="C2" atomName2="H22"/>
   <Bond atomName1="C2" atomName2="H23"/>
  </Residue>
 </Residues>
 <HarmonicBondForce>
  <Bond class1="c3" class2="c3" length="0.15380" k="1945727.36"/>
  <Bond class1="c3" class2="hc" length="0.10970" k="3145687.56"/>
 </HarmonicBondForce>
 <HarmonicAngleForce>
  <Angle class1="c3" class2="c3" class3="hc" angle="1.91637152" k="391.756288"/>
  <Angle class1="hc" class2="c3" class3="hc" angle="1.87762521" k="326.01728"/>
 </HarmonicAngleForce>
 <PeriodicTorsionForce>
  <Proper class1="hc" class2="c3" class3="c3" class4="hc" periodicity1="3" phase1="0.0" k1="0.50208"/>
 </PeriodicTorsionForce>
 <NonbondedForce coulomb14scale="0.833333" lj14scale="0.5">
  <Atom type="0" charge="-0.094100" sigma="0.3397710" epsilon="0.4510352"/>
  <Atom type="1" charge="0.031700" sigma="0.2600177" epsilon="0.0870272"/>
 </NonbondedForce>
</ForceField>

Creating system from structure and forcefield files

pdb        = app.PDBFile('ethane.pdb')
forcefield = app.ForceField('ethane.xml')
system = forcefield.createSystem(pdb.topology, nonbondedMethod=app.NoCutoff, constraints=app.HBonds)
integrator = mm.LangevinIntegrator(298.15*kelvin, 5.0/picoseconds, 2.0*femtoseconds)
integrator.setConstraintTolerance(1e-5)

simulation = app.Simulation(pdb.topology, system, integrator)
simulation.context.setPositions(pdb.positions)
print('Minimizing...')

st = simulation.context.getState(getPositions=True,getEnergy=True)
print(f"Potential energy before minimization is {st.getPotentialEnergy()}")

simulation.minimizeEnergy(maxIterations=100)

st = simulation.context.getState(getPositions=True,getEnergy=True)
print(f"Potential energy after minimization is {st.getPotentialEnergy()}")


print('Equilibration...')
from sys import stdout

simulation.reporters.append(app.StateDataReporter(stdout, 100, step=True, 
    potentialEnergy=True, temperature=True, separator=','))
simulation.context.setVelocitiesToTemperature(150.0*kelvin)
simulation.step(2500)

Production run

import time as time

print('Running Production...')

# Begin timer
tinit=time.time()

# Clear simulation reporters
simulation.reporters.clear()

# Reinitialize simulation reporters. We do this because we want different information printed from the production run than the equilibration run.
# output basic simulation information below every 250000 steps - (which is equal to 2 fs(250,000) = 500,000 fs = 500 ps)
simulation.reporters.append(app.StateDataReporter(stdout, 
                                                  250000, 
                                                  step=True, 
                                                  time=True, 
                                                  potentialEnergy=True, 
                                                  temperature=True, 
                                                  speed=True, 
                                                  separator=','))

# write out a trajectory (i.e., coordinates vs. time) to a DCD
# file every 100 steps - 0.2 ps
simulation.reporters.append(app.DCDReporter('ethane_sim.dcd', 100))

# run the simulation for 1.0x10^7 steps - 20 ns
simulation.step(10000000)

# End timer
tfinal=time.time()
print('Done!')
print('Time required for simulation:', tfinal-tinit, 'seconds')

Analysis

traj = md.load('ethane_sim.dcd', top='ethane.pdb')
atoms, bonds = traj.topology.to_dataframe()
atoms
bond_indices = [0, 4] # atoms to define the bond length
bond_length = md.compute_distances(traj, [bond_indices])

bondcounts, binedges, otherstuff = plt.hist(bond_length, bins=120)
plt.title('C-C bond length histogram')
plt.xlabel('Bond length (nm)')
plt.ylabel('Counts')
plt.show()
phi_indices = [1, 0, 4, 5] # atoms to define the torsion angle
phi = md.compute_dihedrals(traj, [phi_indices])

phicounts, phi_binedges, otherstuff = plt.hist(phi, bins=90) # create a histogram with 90 bins
plt.title('H-C-C-H torsion angle')
plt.xlabel(r'$\phi$ (rad)')
plt.ylabel('Counts')

Potential of Mean Force (PMF)

So far in our analysis, we have looked at the distribution of bond lengths and torsion angles for ethane. However, we can also use our simulations to calculate thermodynamics properties of our system. For example, we can use our calculated distributions along with Boltzmann’s constant to calculate the potential of mean force (pmf), or energy change associated with changes in the bond length or torsion angle.

The potential of mean force is defined by the expression

W(x)=kBTln[p(x)]+CW(x) = -k_{B}T*ln[p(x)] + C

where

p(x)p(x)

is the probability, or the histogram we calculated previously.

For our torsion angle, we can calculate and plot the PMF:

kB = 8.31446/1000 # Boltzmann constant in kJ/mol
Temp = 298.15 # simulation temperature
phicounts[phicounts==0] = 0.1 # get rid of any bins with 0 counts/infinite energy
pmf = -kB*Temp*np.log(phicounts) # W(x) = -kT*ln[p(x)] = -kT*ln[n(x)] + C
pmf = pmf - np.min(pmf) # subtract off minimum value so that energies start from 0

phi_bincenters = (phi_binedges[1:] + phi_binedges[:-1])/2 # compute centers of histogram bins

plt.plot(phi_bincenters, pmf)
plt.title('H-C-C-H torsion pmf')
plt.xlabel(r'$\phi$ (rad)')
plt.ylabel('Relative free energy (kJ/mol)')