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.

nn-body potentials

Classic approach (as approximation of true, quantum-mechanical energy)}

Epot{rI}=V0E_\mathrm{pot}\{\overrightarrow{r}_I\} = V_0 +IV1(rI)+ \sum_I V_1(\overrightarrow{r}_I) +I,JV2(rI,rJ)+ \sum_{I,J} V_2(\overrightarrow{r}_I,\overrightarrow{r}_J) +I,J,KV3(rI,rJ,rK)+ \sum_{I,J, K} V_3(\overrightarrow{r}_I,\overrightarrow{r}_J,\overrightarrow{r}_K) +I,J,K,LV4(rI,rJ,rK,rL)+ \sum_{I,J, K,L} V_4(\overrightarrow{r}_I,\overrightarrow{r}_J,\overrightarrow{r}_K,\overrightarrow{r}_L) + ...

The best choice of potential depends on the state of the system (gas, liquid, solid), and the type of bonds (covalent bonds, non-bonded interactions, crystal/metal lattices)

For non-bonded interactions, a two-body potential is usually a good description already. For bonded interaction, many more terms are needed!

Two-body potentials V2(R)V_2(R)

Noble gases:

  • Lennard-Jones potential 1924: V2(R)=4ϵ[(σR)12(σR)6]V_2(R) = 4\epsilon\left[\left(\frac{\sigma}{R}\right)^{12} - \left(\frac{\sigma}{R}\right)^{6}\right] to model noble gases.

  • Buckingham potential 1938: V2(R)=AeBRCR6V_2(R) = Ae^{-BR} - \frac{C}{R^6} allows for a more flexible form than Lennard-Jones

Covalent (only diatomic) bonds:

  • Morse potential 1929: V2(R)=De[(1eα(RRc))21]V_2(R) = D_e\left[\left(1-e^{-\alpha(R-R_c)}\right)^{2} -1 \right] describes many diatomic systems well. Minimum at RcR_c with the potential energy De-D_e

Solid state:

  • Stillinger-Weber potential 1985: V2(R)=Aϵ[B(σR)p(σR)q]eσRaσV_2(R) = A\epsilon\left[B\left(\frac{\sigma}{R}\right)^{p} - \left(\frac{\sigma}{R}\right)^{q}\right] e^{\frac{\sigma}{R-a\sigma}} and V3(RIJ,RIK,θIJK)=λϵ(cosθIJKcosθ0)2eγσRIJaσeγσRIKaσV_3(R_{IJ},R_{IK},\theta_{IJK}) = \lambda \epsilon (cos\theta_{IJK} - cos\theta_{0})^2 e^{\frac{\gamma\sigma}{R_{IJ}-a\sigma}} e^{\frac{\gamma\sigma}{R_{IK}-a\sigma}} to model solid and liquid phases of Si, GaN, 2D materials, etc.

  • Tersoff’s bond-order potential 1988: V2(RIJ)=fc(RIJ)[fr(RIJ+bIJfa(RIJ)]V_2(R_{IJ}) = f_c(R_{IJ})[f_r(R_{IJ} + b_{IJ}f_a(R_{IJ})] which is a two-body potential where the attractive part is modulated by the bond order bIJb_{IJ} which depends on θIJK\theta_{IJK}

Bond_order.png

Molecular mechanics

Molecular systems (with a bent toward organic molecules in fluid phases)

  • Potential has bonded and non-bonded interactions

  • Atoms have vdW radii, partial charges and possibly polarizabilities

  • Molecules can be treated in an all-atom, united-atom (implicit hydrogen) or coarse-grained (multiple atom form a bead) fashion. Parts of the simulation can also be at the QM-level (QM-MM hybrid approach)

Coarse_grain.png

e.g. OPLS-AA 1996: The potential is a sum of bonded and non-bonded energy terms: V=Vbonded+VnonbondedV = V_\mathrm{bonded} + V_\mathrm{non-bonded}

Bonded terms:

  • Bond-term Vbond=bonds IJKR(I,J)[RIJR0,IJ]2V_\mathrm{bond} = \sum_\mathrm{bonds~IJ} K_R(I,J)[R_{IJ} - R_{0,IJ}]^2

  • Angle-term Vangle=angles IJKKθ(I,J,K)[θIJKθ0,IJK]2V_\mathrm{angle} = \sum_\mathrm{angles~IJK} K_\theta(I,J,K)[\theta_{IJK} - \theta_{0,IJK}]^2

  • Dihedral-term Vdihedral=dihedrals IJKLK12[1+cos(ϕIJKLϕ1)]+K22[1cos(2ϕIJKLϕ2)]+K32[1+cos(3ϕIJKLϕ3)]+K42[1cos(4ϕIJKLϕ4)]V_\mathrm{dihedral} = \sum_\mathrm{dihedrals~IJKL} \frac{K_1}{2}[1 + cos(\phi_{IJKL} - \phi_1)] + \frac{K_2}{2}[1 - cos(2\phi_{IJKL} - \phi_2)] + \frac{K_3}{2}[1 + cos(3\phi_{IJKL} - \phi_3)] + \frac{K_4}{2}[1 - cos(4\phi_{IJKL} - \phi_4)]

Non-Bonded terms: Combined vdW and charge interactions: Vnonbonded=I>JfIJ(AIJRIJ12CIJRIJ6+qIqJe24πϵRIJ)V_\mathrm{non-bonded} = \sum_{I>J} f_{IJ} \left( \frac{A_{IJ}}{R_{IJ}^{12}} - \frac{C_{IJ}}{R_{IJ}^{6}} + \frac{q_I q_J e^2}{4\pi\epsilon R_{IJ}} \right) with combining rules AIJ=AiiAjjA_{IJ}=\sqrt{A_{ii}A_{jj}} and CIJ=CiiCjjC_{IJ}=\sqrt{C_{ii}C_{jj}} and fIJf_{IJ} is 1/0.5/0 if I and J are more than/exactly/less than 3 bonds apart.

Two-body terms in molecular potentials (= bonds)

Bonds.png

Three-body terms in molecular potentials (= angles)

Angle.png

Four-body terms in molecular potentials (= dihredral angles)

Dihedrals.png

In the following, we will generate a trajectory for ethane, and try to reverse-engineer the potential:

from openmm.app import *
from openmm import *
from openmm.unit import *

import MDAnalysis as md
import MDAnalysis.transformations
import MDAnalysis.analysis
import MDAnalysis.analysis.msd as msd
import MDAnalysis.analysis.rdf as rdf

from sys import stdout
import subprocess

import numpy as np
import matplotlib.pyplot as plt
subprocess.run(["wget", "https://raw.githubusercontent.com/hesther/teaching/main/scm_exercise4/ethane.pdb"],stdout = subprocess.DEVNULL,stderr = subprocess.DEVNULL)
subprocess.run(["wget", "https://raw.githubusercontent.com/hesther/teaching/main/scm_exercise4/ethane.xml"],stdout = subprocess.DEVNULL,stderr = subprocess.DEVNULL)
CompletedProcess(args=['wget', 'https://raw.githubusercontent.com/hesther/teaching/main/scm_exercise4/ethane.xml'], returncode=0)
pdb = PDBFile('ethane.pdb')
forcefield = ForceField('ethane.xml')
system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff, constraints=HBonds)
integrator = LangevinIntegrator(298.15*kelvin, 5.0/picoseconds, 2.0*femtoseconds)
integrator.setConstraintTolerance(1e-5)
simulation = 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('Equilibrating...')

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


print('Running Production...')

# Clear simulation reporters
simulation.reporters.clear()

# Reinitialize simulation reporters
simulation.reporters.append(app.StateDataReporter(stdout, 5000, 
    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.dcd', 100))

# run the simulation
simulation.step(100000)
Minimizing...
Potential energy before minimization is 4.902478229380711 kJ/mol
Potential energy after minimization is 4.3898291478741935 kJ/mol
Equilibrating...
#"Step","Potential Energy (kJ/mole)","Temperature (K)"
500,9.964168291421284,296.07841633076924
1000,13.603892433724699,158.16148129574543
1500,33.835767276800944,256.35367325204845
2000,26.857078433685444,191.26451993072845
2500,15.609034600938289,243.75801823514556
Running Production...
#"Step","Time (ps)","Potential Energy (kJ/mole)","Temperature (K)","Speed (ns/day)"
5000,10.000000000000009,23.50267163081441,540.2708428997199,0
10000,19.999999999999794,15.817215479691324,698.6049451979376,29.4
15000,29.99999999999425,11.578670331720101,325.8712992297204,30.2
20000,40.00000000000292,18.676473083050375,225.9719807135084,33.4
25000,50.00000000001514,35.2721186020697,277.3981626933412,36.6
30000,60.00000000002736,12.549529628162745,192.70881506693976,37.9
35000,70.00000000001826,19.499511015385792,314.6460745007441,38.8
40000,79.99999999999496,21.994178776356062,326.5993656762624,40
45000,89.99999999997165,6.9073579017079645,182.56445556391068,40.5
50000,99.99999999994834,11.62858921868106,411.78016244798755,40.8
55000,109.99999999992504,12.012523965528688,264.05024608877466,41.4
60000,119.99999999990173,8.314999146615943,307.42645264457894,41.1
65000,129.99999999989262,11.90288621320278,304.13619365651823,40.6
70000,139.99999999994037,10.081479275725624,329.8475817679048,41.3
75000,149.99999999998812,23.938675960620706,357.1241830960322,41.4
80000,160.00000000003587,20.04996457946585,300.2466752898448,40.8
85000,170.00000000008362,10.900216664059133,293.4716351587902,41.2
90000,180.00000000013137,21.11175804641629,446.9669142260622,41.5
95000,190.0000000001791,15.939904502922495,543.4425677781309,41.8
100000,200.00000000022686,15.659245875611425,573.075286421655,42
u = md.Universe("ethane.pdb", "ethane.dcd")
u.atoms.names
/opt/conda/lib/python3.12/site-packages/MDAnalysis/coordinates/DCD.py:171: DeprecationWarning: DCDReader currently makes independent timesteps by copying self.ts while other readers update self.ts inplace. This behavior will be changed in 3.0 to be the same as other readers. Read more at https://github.com/MDAnalysis/mdanalysis/issues/3889 to learn if this change in behavior might affect you.
  warnings.warn("DCDReader currently makes independent timesteps"
array(['C1', 'C2', 'H11', 'H12', 'H13', 'H21', 'H22', 'H23'], dtype=object)
bond_length = [md.core.topologyobjects.Bond([0,1],u).value() for ts in u.trajectory]
bondcounts, binedges, otherstuff = plt.hist(bond_length, bins=120)
plt.title('C-C bond length histogram')
plt.xlabel('Bond length (Angstrom)')
plt.ylabel('Counts')
plt.show()
<Figure size 640x480 with 1 Axes>

From the distribution of bond lengths, we can compute the potential of mean force, which should approximately recreate the potential we used to make the trajectory (namely the bonded term Ebond=12K(rreq)2E_\mathrm{bond} = \frac{1}{2}{K}(r-r_\mathrm{eq})^2 with req=0.15380r_\mathrm{eq} = 0.15380nm and K=1945727.36K=1945727.36 kJ/mol \cdot nm2^2, values taken from the XML file above).

The potential of mean force is defined as

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

where p(x)p(x) if the probability (the histogram) we calculated above.

def bond(r, K, r0):
    return K/2*(r-r0)**2
    
kB = 8.31446/1000 # Boltzmann constant in kJ/mol K
Temp = 298.15 # simulation temperature

bondcounts[bondcounts==0] = 0.1
pmf = -kB*Temp*np.log(bondcounts)
pmf = pmf - np.min(pmf)

bincenters = (binedges[1:] + binedges[:-1])/2

plt.plot(bincenters, pmf, label='potential of mean force')
plt.xlabel('Bond length (Angstrom)')
plt.ylabel('Relative free energy (kJ/mol)')
plt.title('C-C bond length pmf')

    
r_nm=np.arange(0.1498,0.158,0.001)
r_a=r_nm*10
plt.plot(r_a, bond(r_nm, 1945727.36, 0.15380), label='harmonic potential')
plt.legend()
plt.show()
<Figure size 640x480 with 1 Axes>

Let us also analyze the H-C-C-H torsion. To do so, we only need to change md.core.topologyobjects.Bond to md.core.topologyobjects.Dihedral and use the correct indices.

phi = [md.core.topologyobjects.Dihedral([2,0,1,5],u).value() for ts in u.trajectory]
phicounts, phi_binedges, otherstuff = plt.hist(phi, bins=120)
plt.title('H-C-C-H dihedral angle histogram')
plt.xlabel('Phi (degree)')
plt.ylabel('Counts')
plt.show()
<Figure size 640x480 with 1 Axes>

To compare with the EdihedralE_\mathrm{dihedral} term, we have to remember that there are nine terms that change when we turn along the C-C bond (below, we only followed how a single bond changed, and thus only one term in the energy was affected).

  • H11-C1-C2-H21

  • H11-C1-C2-H22

  • H11-C1-C2-H23

  • H12-C1-C2-H21

  • H12-C1-C2-H22

  • H12-C1-C2-H23

  • H13-C1-C2-H21

  • H13-C1-C2-H22

  • H13-C1-C2-H23

We therefore multiply a single EdihedralE_\mathrm{dihedral} term by 9 to get a good comparison:

def dihedral(phi, K, phi0, n):
    return K*(1+np.cos(n*phi-phi0))

kB = 8.31446/1000 # Boltzmann constant in kJ/mol K
Temp = 298.15 # simulation temperature

phicounts[phicounts==0] = 0.1 
pmf = -kB*Temp*np.log(phicounts) 
pmf = pmf - np.min(pmf) 

phi_bincenters = (phi_binedges[1:] + phi_binedges[:-1])/2 

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

phi_rad=np.arange(-np.pi, np.pi, 0.1)
phi_deg = phi_rad*180/np.pi
plt.plot(phi_deg, dihedral(phi_rad, 0.5021, 0, 3)*9, label='harmonic potential')
plt.legend()
plt.show()
<Figure size 640x480 with 1 Axes>

Partial charges

There is no physical quantity such as an “atomic charge”, there are therefore many valid schemes to assign partial charges, e.g. (among many others)

  • Formal charges

  • Restrained fit to the electrostatic potential (RESP)

  • Mulliken charges

  • Bader charges

Partial.png

Polarizability

Partial.png

Reactive (molecular) force fields

For LJ, Buckingham, Morse-type FFs, reactivity is not a problem:

Reactive.png

But for molecular force fields, because we change the Morse term to an harmonic term, all bonds become unbreakable

Possible solutions:

  • General reactive force fields based on bond-order potentials, for example ReaxFF

  • Force fields without enforced functional form, e.g. from machine learning

  • For simple cases, e.g. protonation reactions, one can also work with ghost atoms

A potential for water

There are nearly 50 water potentials reported in literature, which follow the general forms

Water.png

Yet, none of them capture all the properties of water well:

Untitled-2.jpg (from https://water.lsbu.ac.uk/water/water_models.html)