Repository: openmm/openmm — pull request #3493 (cpp)
Title: Ensure bond lengths are set before creating forces that depend on them

Fixes #3487.

Resolved issues:
title: Switching the order of forcefield.xml files causes simulation to crash
body: Hi @peastman,

I have a rather strange bug happening for me. I have (finally) tracked it down to a switching of the strings when defining the forcefield. For example, if I specify:
`forcefield = ForceField("amoeba2018.xml", "formamide.xml")`
the simulation will run for a bit, then crash. However, If I switch the order of those and instead use:
`forcefield = ForceField("formamide.xml", "amoeba2018.xml")`
the simulation works as expected.


I am using OpenMM built from source at the current point on master.

Here are some test files (reference platform) that reproduce the error:

[fails_nve.py.txt](https://github.com/openmm/openmm/files/8150049/fails_nve.py.txt)
[formamide.xml.txt](https://github.com/openmm/openmm/files/8150050/formamide.xml.txt)
[system.pdb.txt](https://github.com/openmm/openmm/files/8150051/system.pdb.txt)
[works_nve.py.txt](https://github.com/openmm/openmm/files/8150052/works_nve.py.txt)

Below are the output from the various platforms. Usually it seems like the energy terms spike massively just before it crashes, but I don't understand why the order of the .xml files would do this.

Thanks,

Gareth


### Reference:
`#"Step","Potential Energy (kJ/mole)","Kinetic Energy (kJ/mole)","Total Energy (kJ/mole)","Temperature (K)","Box Volume (nm^3)","Density (g/mL)"`
...
`100,-53.494514506154964,27.039465172753232,-26.455049333401732,433.6133540563737,1.030301,0.07259270846536811
110,-27.236485666785786,3.3086763563983093,-23.927809310387477,53.05897299442808,1.030301,0.07259270846536811
Segmentation fault (core dumped)`
### OpenCL:
`100,-53.23412222478068,26.82700063288212,-26.40712159189856,430.2062059799246,1.030301,0.07259270846536811
110,-32.92316336247599,6.56602069735527,-26.35714266512072,105.29476967069344,1.030301,0.07259270846536811
120,1.4365499225153265e+23,31547775385600.0,1.4365499228308042e+23,505910032264674.25,1.030301,0.07259270846536811
130,9.812762097322067e+28,904854700294144.0,9.812762097322157e+28,1.4510534103447568e+16,1.030301,0.07259270846536811
140,1.4205908385270204e+31,3358057606152192.0,1.4205908385270207e+31,5.385086622147509e+16,1.030301,0.07259270846536811
150,2.933014196877673e+32,6956549875433472.0,2.933014196877673e+32,1.1155741819874218e+17,1.030301,0.07259270846536811`

`Traceback (most recent call last):
  File "/lustre/rcghead1/users/c3162723/testing/ommTesting/newParamTests/formamide_version_testing/single_molecule/nve/opencl/./fails_nve.py", line 32, in <module>
    simulation.step(600000)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/simulation.py", line 141, in step
    self._simulate(endStep=self.currentStep+steps)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/simulation.py", line 210, in _simulate
    self.integrator.step(stepsToGo)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/openmm.py", line 11636, in step
    return _openmm.VerletIntegrator_step(self, steps)
openmm.OpenMMException: clWaitForEvents`

### CUDA:
`90,-40.2270726642338,13.860071877017617,-26.367000787216185,222.26446476138204,1.030301,0.07259270846536811
100,-53.23090262028849,26.823644541203976,-26.407258079084514,430.15238663994205,1.030301,0.07259270846536811
110,-32.94177145573771,6.584286147728562,-26.357485308009146,105.58768016833501,1.030301,0.07259270846536811
120,6.239121191292798e+23,33425676472320.0,6.239121191627055e+23,536024643762955.44,1.030301,0.07259270846536811
130,4.826764310943093e+28,645174452781056.0,4.826764310943157e+28,1.0346220113250622e+16,1.030301,0.07259270846536811
140,7.7064977143566e+30,2938203698364416.0,7.706497714356603e+30,4.711795711967144e+16,1.030301,0.07259270846536811`


`Traceback (most recent call last):
  File "/lustre/rcghead1/users/c3162723/testing/ommTesting/newParamTests/formamide_version_testing/single_molecule/nve/cuda/./fails_nve.py", line 32, in <module>
    simulation.step(600000)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/simulation.py", line 141, in step
    self._simulate(endStep=self.currentStep+steps)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/simulation.py", line 243, in _simulate
    self._generate_reports(unwrapped, False)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/simulation.py", line 263, in _generate_reports
    reporter.report(self, state)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/statedatareporter.py", line 206, in report
    self._checkForErrors(simulation, state)
  File "/home/c3162723/software/src/my_openmm_fork/install/gnu8dev/28Feb22/pythonInstall/lib/python3.9/site-packages/openmm/app/statedatareporter.py", line 361, in _checkForErrors
    raise ValueError('Energy is NaN')
ValueError: Energy is NaN`

### CPU:
`80,-32.70232472144512,6.305932946930092,-26.39639177451503,101.12392083583997,1.030301,0.07259270846536811
90,-43.12688017527704,16.725968222782672,-26.400911952494365,268.2228784698495,1.030301,0.07259270846536811
100,-53.494514506154964,27.039465172753232,-26.455049333401732,433.6133540563737,1.030301,0.07259270846536811
110,-27.236485666785786,3.3086763563983093,-23.927809310387477,53.05897299442808,1.030301,0.07259270846536811
Segmentation fault      (core dumped)`



## IMPORTANT INSTRUCTIONS

1. If you edit a python file, you MUST run 'make PythonInstall' inside the build directory
2. If you edit a c++ file, you MUST run 'make -j4 && make install" inside the build directory

Hints:

Fix patch:
diff --git a/wrappers/python/openmm/app/forcefield.py b/wrappers/python/openmm/app/forcefield.py
index 19c69a6735..897678a878 100644
--- a/wrappers/python/openmm/app/forcefield.py
+++ b/wrappers/python/openmm/app/forcefield.py
@@ -2057,6 +2057,11 @@ def parseElement(element, ff):
             generator.registerAngle(angle.attrib)
 
     def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
+        pass
+
+    def postprocessSystem(self, sys, data, args):
+        # We need to wait until after all bonds have been added so their lengths will be set correctly.
+
         existing = [f for f in sys.getForces() if type(f) == mm.HarmonicAngleForce]
         if len(existing) == 0:
             force = mm.HarmonicAngleForce()
@@ -3733,8 +3738,11 @@ def parseElement(element, forceField):
     #=============================================================================================
 
     def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
+        self._nonbondedMethod = nonbondedMethod
+        self._nonbondedCutoff = nonbondedCutoff
 
-        # get force
+    def postprocessSystem(self, sys, data, args):
+        # We need to wait until after all bonds have been added so their lengths will be set correctly.
 
         energy = """k*(theta^2 + %s*theta^3 + %s*theta^4 + %s*theta^5 + %s*theta^6);
                     theta = %.15g*pointangle(x2, y2, z2, x4, y4, z4, projx, projy, projz);
@@ -3857,12 +3865,12 @@ def addBond(particles):
 
         for force in self.forceField._forces:
             if (force.__class__.__name__ == 'AmoebaAngleGenerator'):
-                force.createForcePostOpBendAngle(sys, data, nonbondedMethod, nonbondedCutoff, angles, args)
-                force.createForcePostOpBendInPlaneAngle(sys, data, nonbondedMethod, nonbondedCutoff, angles, args)
+                force.createForcePostOpBendAngle(sys, data, self._nonbondedMethod, self._nonbondedCutoff, angles, args)
+                force.createForcePostOpBendInPlaneAngle(sys, data, self._nonbondedMethod, self._nonbondedCutoff, angles, args)
 
         for force in self.forceField._forces:
             if (force.__class__.__name__ == 'AmoebaStretchBendGenerator'):
-                force.createForcePostAmoebaBondForce(sys, data, nonbondedMethod, nonbondedCutoff, angles, args)
+                force.createForcePostAmoebaBondForce(sys, data, self._nonbondedMethod, self._nonbondedCutoff, angles, args)
 
 parsers["AmoebaOutOfPlaneBendForce"] = AmoebaOutOfPlaneBendGenerator.parseElement
 
@@ -4672,7 +4680,6 @@ def createForcePostAmoebaBondForce(self, sys, data, nonbondedMethod, nonbondedCu
                             k1, k2 = self.k1[i], self.k2[i]
                         else:
                             k1, k2 = self.k2[i], self.k1[i]
-                            
                         force.addBond((angle[0], angle[1], angle[2]), (bondAB, bondCB, angleDict['idealAngle']/radian, k1, k2))
 
                     break


Test patch:
diff --git a/wrappers/python/tests/TestForceField.py b/wrappers/python/tests/TestForceField.py
index f78445af2f..a0f83b909b 100644
--- a/wrappers/python/tests/TestForceField.py
+++ b/wrappers/python/tests/TestForceField.py
@@ -1020,7 +1020,7 @@ def test_ImpropersOrdering(self):
         system1 = ff1.createSystem(pdb.topology)
         system2 = ff2.createSystem(pdb.topology)
 
-        imp1 = system1.getForce(2).getTorsionParameters(158)
+        imp1 = system1.getForce(1).getTorsionParameters(158)
         imp2 = system2.getForce(0).getTorsionParameters(158)
 
         system1_indexes = [imp1[0], imp1[1], imp1[2], imp1[3]]