View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.openmm;
39  
40  import edu.uiowa.jopenmm.OpenMM_Vec3;
41  import ffx.crystal.Crystal;
42  import ffx.openmm.AndersenThermostat;
43  import ffx.openmm.CMMotionRemover;
44  import ffx.openmm.Force;
45  import ffx.openmm.MonteCarloBarostat;
46  import ffx.potential.MolecularAssembly;
47  import ffx.potential.bonded.Angle;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.Bond;
50  import ffx.potential.nonbonded.GeneralizedKirkwood;
51  import ffx.potential.nonbonded.VanDerWaals;
52  import ffx.potential.nonbonded.VanDerWaalsForm;
53  import ffx.potential.nonbonded.implicit.ChandlerCavitation;
54  import ffx.potential.nonbonded.implicit.DispersionRegion;
55  import ffx.potential.parameters.BondType;
56  import ffx.potential.parameters.ForceField;
57  import ffx.utilities.Constants;
58  import org.apache.commons.configuration2.CompositeConfiguration;
59  
60  import javax.annotation.Nullable;
61  import java.util.logging.Level;
62  import java.util.logging.Logger;
63  
64  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
65  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_True;
66  import static ffx.potential.nonbonded.VanDerWaalsForm.VDW_TYPE.LENNARD_JONES;
67  import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
68  import static ffx.utilities.Constants.kB;
69  import static java.lang.String.format;
70  import static org.apache.commons.math3.util.FastMath.cos;
71  import static org.apache.commons.math3.util.FastMath.pow;
72  import static org.apache.commons.math3.util.FastMath.sqrt;
73  import static org.apache.commons.math3.util.FastMath.toRadians;
74  
75  /**
76   * Create and manage an OpenMM System.
77   *
78   * <p>The definition of a System involves four elements:
79   *
80   * <p>The particles and constraints are defined directly by the System object, while forces are
81   * defined by objects that extend the Force class. After creating a System, call addParticle() once
82   * for each particle, addConstraint() for each constraint, and addForce() for each Force.
83   *
84   * <p>In addition, particles may be designated as "virtual sites". These are particles whose
85   * positions are computed automatically based on the positions of other particles. To define a
86   * virtual site, call setVirtualSite(), passing in a VirtualSite object that defines the rules for
87   * computing its position.
88   */
89  public class OpenMMSystem extends ffx.openmm.System {
90  
91    private static final Logger logger = Logger.getLogger(OpenMMSystem.class.getName());
92  
93    private static final double DEFAULT_MELD_SCALE_FACTOR = -1.0;
94    /**
95     * The ForceFieldEnergyOpenMM instance.
96     */
97    private final OpenMMEnergy openMMEnergy;
98    /**
99     * The OpenMMContext instance.
100    */
101   private final OpenMMContext openMMContext;
102   private final double meldScaleFactor;
103   /**
104    * When using MELD, our goal will be to scale down the potential by this factor. A negative value
105    * indicates we're not using MELD.
106    */
107   private final boolean useMeld;
108   /**
109    * The Force Field in use.
110    */
111   private final ForceField forceField;
112   /**
113    * Array of atoms in the system.
114    */
115   private final Atom[] atoms;
116   /**
117    * This flag indicates bonded force constants and equilibria are updated (e.g. during ManyBody
118    * titration).
119    */
120   private boolean updateBondedTerms = false;
121   /**
122    * OpenMM Custom Bond Force
123    */
124   private BondForce bondForce = null;
125   /**
126    * OpenMM Custom Angle Force
127    */
128   private AngleForce angleForce = null;
129   /**
130    * OpenMM Custom In-Plane Angle Force
131    */
132   private InPlaneAngleForce inPlaneAngleForce = null;
133   /**
134    * OpenMM Custom Stretch-Bend Force
135    */
136   private StretchBendForce stretchBendForce = null;
137   /**
138    * OpenMM Custom Urey-Bradley Force
139    */
140   private UreyBradleyForce ureyBradleyForce = null;
141   /**
142    * OpenMM Custom Out-of-Plane Bend Force
143    */
144   private OutOfPlaneBendForce outOfPlaneBendForce = null;
145   /**
146    * OpenMM Custom Pi-Torsion Force
147    */
148   private PiOrbitalTorsionForce piOrbitalTorsionForce = null;
149   /**
150    * OpenMM AMOEBA Torsion Force.
151    */
152   private TorsionForce torsionForce = null;
153   /**
154    * OpenMM Improper Torsion Force.
155    */
156   private ImproperTorsionForce improperTorsionForce = null;
157   /**
158    * OpenMM Torsion-Torsion Force.
159    */
160   private AmoebaTorsionTorsionForce amoebaTorsionTorsionForce = null;
161   /**
162    * OpenMM Stretch-Torsion Force.
163    */
164   private StretchTorsionForce stretchTorsionForce = null;
165   /**
166    * OpenMM Angle-Torsion Force.
167    */
168   private AngleTorsionForce angleTorsionForce = null;
169   /**
170    * OpenMM Restraint-Torsion Force.
171    */
172   private RestrainTorsionsForce restrainTorsionsForce = null;
173   /**
174    * OpenMM Restrain-Position Force.
175    */
176   private RestrainPositionsForce restrainPositionsForce = null;
177   /**
178    * OpenMM Restrain-Groups Force.
179    */
180   private RestrainGroupsForce restrainGroupsForce = null;
181   /**
182    * OpenMM AMOEBA van der Waals Force.
183    */
184   private AmoebaVdwForce amoebaVDWForce = null;
185   /**
186    * OpenMM AMOEBA Multipole Force.
187    */
188   private AmoebaMultipoleForce amoebaMultipoleForce = null;
189   /**
190    * OpenMM Generalized Kirkwood Force.
191    */
192   private AmoebaGeneralizedKirkwoodForce amoebaGeneralizedKirkwoodForce = null;
193   /**
194    * OpenMM AMOEBA WCA Dispersion Force.
195    */
196   private AmoebaWcaDispersionForce amoebaWcaDispersionForce = null;
197   /**
198    * OpenMM AMOEBA WCA Cavitation Force.
199    */
200   private AmoebaGKCavitationForce amoebaGKCavitationForce = null;
201   /**
202    * OpenMM Custom GB Force.
203    */
204   private FixedChargeGBForce fixedChargeGBForce = null;
205   /**
206    * OpenMM Fixed Charge Non-Bonded Force.
207    */
208   private FixedChargeNonbondedForce fixedChargeNonBondedForce = null;
209   /**
210    * Custom forces to handle alchemical transformations for fixed charge systems.
211    */
212   private FixedChargeAlchemicalForces fixedChargeAlchemicalForces = null;
213   /**
214    * OpenMM thermostat. Currently, an Andersen thermostat is supported.
215    */
216   private AndersenThermostat andersenThermostat = null;
217   /**
218    * Barostat to be added if NPT (isothermal-isobaric) dynamics is requested.
219    */
220   private MonteCarloBarostat monteCarloBarostat = null;
221   /**
222    * OpenMM center-of-mass motion remover.
223    */
224   private CMMotionRemover cmMotionRemover = null;
225   /**
226    * Fixed charge softcore vdW force boolean.
227    */
228   private boolean softcoreCreated = false;
229   /**
230    * Lambda flag to indicate control of electrostatic scaling. If both elec and vdW are being
231    * scaled, then vdW is scaled first, followed by elec.
232    */
233   private boolean elecLambdaTerm;
234   /**
235    * Lambda flag to indicate control of vdW scaling. If both elec and vdW are being scaled, then
236    * vdW is scaled first, followed by elec.
237    */
238   private boolean vdwLambdaTerm;
239   /**
240    * Lambda flag to indicate control of torsional force constants (L=0 corresponds to torsions
241    * being off, and L=1 to torsions at full strength).
242    */
243   private boolean torsionLambdaTerm;
244   /**
245    * Value of the van der Waals lambda state variable.
246    */
247   private double lambdaVDW = 1.0;
248   /**
249    * Value of the electrostatics lambda state variable.
250    */
251   private double lambdaElec = 1.0;
252   /**
253    * Value of the electrostatics lambda state variable.
254    */
255   private double lambdaTorsion = 1.0;
256   /**
257    * The lambda value that defines when the electrostatics will start to turn on for full path
258    * non-bonded term scaling.
259    *
260    * <p>A value of 0.6 works well for Chloride ion solvation, which is a difficult case due to the
261    * ion having a formal negative charge and a large polarizability.
262    */
263   private double electrostaticStart = 0.6;
264   /**
265    * Electrostatics lambda is raised to this power.
266    */
267   private double electrostaticLambdaPower;
268   /**
269    * van der Waals softcore alpha.
270    */
271   private double vdWSoftcoreAlpha = 0.25;
272   /**
273    * van der Waals softcore beta.
274    */
275   private double vdwSoftcorePower = 3.0;
276   /**
277    * Torsional lambda power.
278    */
279   private double torsionalLambdaPower = 2.0;
280 
281   /**
282    * OpenMMSystem constructor.
283    *
284    * @param openMMEnergy ForceFieldEnergyOpenMM instance.
285    */
286   public OpenMMSystem(OpenMMEnergy openMMEnergy) {
287     this.openMMEnergy = openMMEnergy;
288     this.openMMContext = openMMEnergy.getContext();
289 
290     logger.info("\n System created");
291 
292     MolecularAssembly molecularAssembly = openMMEnergy.getMolecularAssembly();
293     forceField = molecularAssembly.getForceField();
294     atoms = molecularAssembly.getAtomArray();
295 
296     // Load atoms.
297     try {
298       addAtoms();
299     } catch (Exception e) {
300       logger.severe(" Atom without mass encountered.");
301     }
302 
303     // Check for MELD use. If we're using MELD, set all lambda terms to true.
304     meldScaleFactor = forceField.getDouble("MELD_SCALE_FACTOR", DEFAULT_MELD_SCALE_FACTOR);
305     if (meldScaleFactor <= 1.0 && meldScaleFactor > 0.0) {
306       useMeld = true;
307       elecLambdaTerm = true;
308       vdwLambdaTerm = true;
309       torsionLambdaTerm = true;
310     } else {
311       useMeld = false;
312       elecLambdaTerm = false;
313       vdwLambdaTerm = false;
314       torsionLambdaTerm = false;
315     }
316 
317     // Read alchemical information -- this needs to be done before creating forces.
318     elecLambdaTerm = forceField.getBoolean("ELEC_LAMBDATERM", elecLambdaTerm);
319     vdwLambdaTerm = forceField.getBoolean("VDW_LAMBDATERM", vdwLambdaTerm);
320     torsionLambdaTerm = forceField.getBoolean("TORSION_LAMBDATERM", torsionLambdaTerm);
321 
322     if (!forceField.getBoolean("LAMBDATERM", false)) {
323       openMMEnergy.setLambdaTerm(elecLambdaTerm || vdwLambdaTerm || torsionLambdaTerm);
324     } else {
325       openMMEnergy.setLambdaTerm(true);
326     }
327 
328     VanDerWaals vdW = openMMEnergy.getVdwNode();
329     if (vdW != null) {
330       vdWSoftcoreAlpha = vdW.getAlpha();
331       vdwSoftcorePower = (int) vdW.getBeta();
332     }
333 
334     electrostaticStart = forceField.getDouble("PERMANENT_LAMBDA_START", electrostaticStart);
335     if (electrostaticStart > 1.0) {
336       electrostaticStart = 1.0;
337     } else if (electrostaticStart < 0.0) {
338       electrostaticStart = 0.0;
339     }
340     electrostaticLambdaPower = forceField.getDouble("PERMANENT_LAMBDA_EXPONENT", 2.0);
341 
342     if (useMeld) {
343       // lambda path starts at 0.0
344       openMMEnergy.setLambdaStart(0.0);
345       // electrostaticStart is ignored for MELD.
346       electrostaticStart = 0.0;
347       // electrostaticLambdaPower is ignored for MELD.
348       electrostaticLambdaPower = 1.0;
349       // vdW is linearly scaled for MELD.
350       vdwSoftcorePower = 1;
351       // No softcore offset for MELD.
352       vdWSoftcoreAlpha = 0.0;
353       // Torsions are linearly scaled for MELD.
354       torsionalLambdaPower = 1.0;
355       // Only need single-sided dU/dL
356       openMMEnergy.setTwoSidedFiniteDifference(false);
357     }
358   }
359 
360   /**
361    * Add forces to the system.
362    */
363   public void addForces() {
364 
365     // Set up rigid constraints.
366     // These flags need to be set before bonds and angles are created below.
367     boolean rigidHydrogen = forceField.getBoolean("RIGID_HYDROGEN", false);
368     boolean rigidBonds = forceField.getBoolean("RIGID_BONDS", false);
369     boolean rigidHydrogenAngles = forceField.getBoolean("RIGID_HYDROGEN_ANGLES", false);
370     if (rigidHydrogen) {
371       addHydrogenConstraints();
372     }
373     if (rigidBonds) {
374       addUpBondConstraints();
375     }
376     if (rigidHydrogenAngles) {
377       setUpHydrogenAngleConstraints();
378     }
379 
380     logger.info("\n Bonded Terms\n");
381 
382     // Add Bond Force.
383     if (rigidBonds) {
384       logger.info(" Not creating AmoebaBondForce because bonds are constrained.");
385     } else {
386       bondForce = (BondForce) BondForce.constructForce(openMMEnergy);
387       addForce(bondForce);
388     }
389 
390     // Add Angle Force.
391     angleForce = (AngleForce) AngleForce.constructForce(openMMEnergy);
392     addForce(angleForce);
393 
394     // Add In-Plane Angle Force.
395     inPlaneAngleForce = (InPlaneAngleForce) InPlaneAngleForce.constructForce(openMMEnergy);
396     addForce(inPlaneAngleForce);
397 
398     // Add Stretch-Bend Force.
399     stretchBendForce = (StretchBendForce) StretchBendForce.constructForce(openMMEnergy);
400     addForce(stretchBendForce);
401 
402     // Add Urey-Bradley Force.
403     ureyBradleyForce = (UreyBradleyForce) UreyBradleyForce.constructForce(openMMEnergy);
404     addForce(ureyBradleyForce);
405 
406     // Out-of Plane Bend Force.
407     outOfPlaneBendForce = (OutOfPlaneBendForce) OutOfPlaneBendForce.constructForce(openMMEnergy);
408     addForce(outOfPlaneBendForce);
409 
410     // Add Pi-Torsion Force.
411     piOrbitalTorsionForce = (PiOrbitalTorsionForce) PiOrbitalTorsionForce.constructForce(openMMEnergy);
412     addForce(piOrbitalTorsionForce);
413 
414     // Add Torsion Force.
415     torsionForce = (TorsionForce) TorsionForce.constructForce(openMMEnergy);
416     addForce(torsionForce);
417 
418     // Add Improper Torsion Force.
419     improperTorsionForce = (ImproperTorsionForce) ImproperTorsionForce.constructForce(openMMEnergy);
420     addForce(improperTorsionForce);
421 
422     // Add Stretch-Torsion coupling terms.
423     stretchTorsionForce = (StretchTorsionForce) StretchTorsionForce.constructForce(openMMEnergy);
424     addForce(stretchTorsionForce);
425 
426     // Add Angle-Torsion coupling terms.
427     angleTorsionForce = (AngleTorsionForce) AngleTorsionForce.constructForce(openMMEnergy);
428     addForce(angleTorsionForce);
429 
430     // Add Torsion-Torsion Force.
431     amoebaTorsionTorsionForce = (AmoebaTorsionTorsionForce) AmoebaTorsionTorsionForce.constructForce(openMMEnergy);
432     addForce(amoebaTorsionTorsionForce);
433 
434     // Add Restrain-Position force.
435     restrainPositionsForce = (RestrainPositionsForce) RestrainPositionsForce.constructForce(openMMEnergy);
436     addForce(restrainPositionsForce);
437 
438     // Add a Restrain-Bond force for each functional form.
439     for (BondType.BondFunction function : BondType.BondFunction.values()) {
440       RestrainBondsForce restrainBondsForce = (RestrainBondsForce) RestrainBondsForce.constructForce(function, openMMEnergy);
441       addForce(restrainBondsForce);
442     }
443 
444     // Add Restraint-Torsions
445     restrainTorsionsForce = (RestrainTorsionsForce) RestrainTorsionsForce.constructForce(openMMEnergy);
446     addForce(restrainTorsionsForce);
447 
448     // Add Restrain-Groups force.
449     restrainGroupsForce = (RestrainGroupsForce) RestrainGroupsForce.constructForce(openMMEnergy);
450     addForce(restrainGroupsForce);
451 
452     setDefaultPeriodicBoxVectors();
453 
454     VanDerWaals vdW = openMMEnergy.getVdwNode();
455     if (vdW != null) {
456       logger.info("\n Non-Bonded Terms\n");
457       VanDerWaalsForm vdwForm = vdW.getVDWForm();
458       if (vdwForm.vdwType == LENNARD_JONES) {
459         fixedChargeNonBondedForce = (FixedChargeNonbondedForce) FixedChargeNonbondedForce.constructForce(openMMEnergy);
460         addForce(fixedChargeNonBondedForce);
461         if (fixedChargeNonBondedForce != null) {
462           GeneralizedKirkwood gk = openMMEnergy.getGK();
463           if (gk != null) {
464             fixedChargeGBForce = (FixedChargeGBForce) FixedChargeGBForce.constructForce(openMMEnergy);
465             addForce(fixedChargeGBForce);
466           }
467         }
468       } else {
469         // Add vdW Force.
470         amoebaVDWForce = (AmoebaVdwForce) AmoebaVdwForce.constructForce(openMMEnergy);
471         addForce(amoebaVDWForce);
472 
473         // Add Multipole Force.
474         amoebaMultipoleForce = (AmoebaMultipoleForce) AmoebaMultipoleForce.constructForce(openMMEnergy);
475         addForce(amoebaMultipoleForce);
476 
477         // Add Generalized Kirkwood Force.
478         GeneralizedKirkwood gk = openMMEnergy.getGK();
479         if (gk != null) {
480           amoebaGeneralizedKirkwoodForce = (AmoebaGeneralizedKirkwoodForce) AmoebaGeneralizedKirkwoodForce.constructForce(openMMEnergy);
481           addForce(amoebaGeneralizedKirkwoodForce);
482 
483           // Add WCA Dispersion Force.
484           DispersionRegion dispersionRegion = gk.getDispersionRegion();
485           if (dispersionRegion != null) {
486             amoebaWcaDispersionForce = (AmoebaWcaDispersionForce) AmoebaWcaDispersionForce.constructForce(openMMEnergy);
487             addForce(amoebaWcaDispersionForce);
488           }
489 
490           // Add a GaussVol Cavitation Force.
491           ChandlerCavitation chandlerCavitation = gk.getChandlerCavitation();
492           if (chandlerCavitation != null && chandlerCavitation.getGaussVol() != null) {
493             amoebaGKCavitationForce = (AmoebaGKCavitationForce) AmoebaGKCavitationForce.constructForce(openMMEnergy);
494             addForce(amoebaGKCavitationForce);
495           }
496         }
497       }
498     }
499 
500     if (openMMEnergy.getLambdaTerm()) {
501       logger.info(format("\n Lambda path start:              %6.3f", openMMEnergy.getLambdaStart()));
502       logger.info(format(" Lambda scales torsions:          %s", torsionLambdaTerm));
503       if (torsionLambdaTerm) {
504         logger.info(format(" torsion lambda power:           %6.3f", torsionalLambdaPower));
505       }
506       logger.info(format(" Lambda scales vdW interactions:  %s", vdwLambdaTerm));
507       if (vdwLambdaTerm) {
508         logger.info(format(" van Der Waals alpha:            %6.3f", vdWSoftcoreAlpha));
509         logger.info(format(" van Der Waals lambda power:     %6.3f", vdwSoftcorePower));
510       }
511       logger.info(format(" Lambda scales electrostatics:    %s", elecLambdaTerm));
512 
513       if (elecLambdaTerm) {
514         logger.info(format(" Electrostatics start:           %6.3f", electrostaticStart));
515         logger.info(format(" Electrostatics lambda power:    %6.3f", electrostaticLambdaPower));
516       }
517       logger.info(format(" Using Meld:                      %s", useMeld));
518       if (useMeld) {
519         logger.info(format(" Meld scale factor:              %6.3f", meldScaleFactor));
520       }
521     }
522   }
523 
524   /**
525    * Remove a force from the OpenMM System.
526    * The OpenMM memory associated with the removed Force object is deleted.
527    */
528   public void removeForce(Force force) {
529     if (force != null) {
530       removeForce(force.getForceIndex());
531     }
532   }
533 
534   /**
535    * Add an Andersen thermostat to the system.
536    *
537    * @param targetTemp Target temperature in Kelvins.
538    */
539   public void addAndersenThermostatForce(double targetTemp) {
540     double collisionFreq = forceField.getDouble("COLLISION_FREQ", 0.1);
541     addAndersenThermostatForce(targetTemp, collisionFreq);
542   }
543 
544   /**
545    * Add an Andersen thermostat to the system.
546    *
547    * @param targetTemp    Target temperature in Kelvins.
548    * @param collisionFreq Collision frequency in 1/psec.
549    */
550   public void addAndersenThermostatForce(double targetTemp, double collisionFreq) {
551     if (andersenThermostat != null) {
552       removeForce(andersenThermostat);
553       andersenThermostat = null;
554     }
555     andersenThermostat = new AndersenThermostat(targetTemp, collisionFreq);
556     addForce(andersenThermostat);
557     logger.info("\n Adding an Andersen thermostat");
558     logger.info(format("  Target Temperature:   %6.2f (K)", targetTemp));
559     logger.info(format("  Collision Frequency:  %6.2f (1/psec)", collisionFreq));
560   }
561 
562   /**
563    * Adds a force that removes center-of-mass motion.
564    */
565   public void addCOMMRemoverForce() {
566     if (cmMotionRemover != null) {
567       removeForce(cmMotionRemover);
568       cmMotionRemover = null;
569     }
570     int frequency = 100;
571     cmMotionRemover = new CMMotionRemover(frequency);
572     addForce(cmMotionRemover);
573     logger.info("\n Added a Center of Mass Motion Remover");
574     logger.info(format("  Frequency:            %6d", frequency));
575   }
576 
577   /**
578    * Add a Monte Carlo Barostat to the system.
579    *
580    * @param targetPressure The target pressure (in atm).
581    * @param targetTemp     The target temperature.
582    * @param frequency      The frequency to apply the barostat.
583    */
584   public void addMonteCarloBarostatForce(double targetPressure, double targetTemp, int frequency) {
585     if (monteCarloBarostat != null) {
586       removeForce(monteCarloBarostat);
587       monteCarloBarostat = null;
588     }
589     double pressureInBar = targetPressure * Constants.ATM_TO_BAR;
590     monteCarloBarostat = new MonteCarloBarostat(pressureInBar, targetTemp, frequency);
591     CompositeConfiguration properties = openMMEnergy.getMolecularAssembly().getProperties();
592     if (properties.containsKey("barostat-seed")) {
593       int randomSeed = properties.getInt("barostat-seed", 0);
594       logger.info(format(" Setting random seed %d for Monte Carlo Barostat", randomSeed));
595       monteCarloBarostat.setRandomNumberSeed(randomSeed);
596     }
597     addForce(monteCarloBarostat);
598     logger.info("\n Added a Monte Carlo Barostat");
599     logger.info(format("  Target Pressure:      %6.2f (atm)", targetPressure));
600     logger.info(format("  Target Temperature:   %6.2f (K)", targetTemp));
601     logger.info(format("  MC Move Frequency:    %6d", frequency));
602   }
603 
604   /**
605    * Calculate the number of degrees of freedom.
606    *
607    * @return Number of degrees of freedom.
608    */
609   public int calculateDegreesOfFreedom() {
610     // Begin from the 3 times the number of active atoms.
611     int dof = openMMEnergy.getNumberOfVariables();
612     // Remove OpenMM constraints.
613     dof = dof - getNumConstraints();
614     // Remove center of mass motion.
615     if (cmMotionRemover != null) {
616       dof -= 3;
617     }
618     return dof;
619   }
620 
621   public double getTemperature(double kineticEnergy) {
622     double dof = calculateDegreesOfFreedom();
623     return 2.0 * kineticEnergy * KCAL_TO_GRAM_ANG2_PER_PS2 / (kB * dof);
624   }
625 
626   /**
627    * Get the ForceField in use.
628    *
629    * @return ForceField instance.
630    */
631   public ForceField getForceField() {
632     return forceField;
633   }
634 
635   /**
636    * Destroy the system.
637    */
638   public void free() {
639     if (getPointer() != null) {
640       logger.fine(" Free OpenMM system.");
641       destroy();
642       logger.fine(" Free OpenMM system completed.");
643     }
644   }
645 
646   /**
647    * Print current lambda values.
648    */
649   public void printLambdaValues() {
650     logger.info(format("\n Lambda Values\n Torsion: %6.3f vdW: %6.3f Elec: %6.3f ", lambdaTorsion,
651         lambdaVDW, lambdaElec));
652   }
653 
654   /**
655    * Set the overall lambda value for the system.
656    *
657    * @param lambda Current lambda value.
658    */
659   public void setLambda(double lambda) {
660 
661     // Initially set all lambda values to 1.0.
662     lambdaTorsion = 1.0;
663 
664     // Applied to softcore vdW forces.
665     lambdaVDW = 1.0;
666 
667     // Applied to normal electrostatic parameters for alchemical atoms.
668     lambdaElec = 1.0;
669 
670     if (torsionLambdaTerm) {
671       // Multiply torsional potentials by L^2 (dU/dL = 0 at L=0).
672       lambdaTorsion = pow(lambda, torsionalLambdaPower);
673       if (useMeld) {
674         lambdaTorsion = meldScaleFactor + lambda * (1.0 - meldScaleFactor);
675       }
676     }
677 
678     if (elecLambdaTerm && vdwLambdaTerm) {
679       // Lambda effects both vdW and electrostatics.
680       if (lambda < electrostaticStart) {
681         // Begin turning vdW on with electrostatics off.
682         lambdaElec = 0.0;
683       } else {
684         // Turn electrostatics on during the latter part of the path.
685         double elecWindow = 1.0 - electrostaticStart;
686         lambdaElec = (lambda - electrostaticStart) / elecWindow;
687         lambdaElec = pow(lambdaElec, electrostaticLambdaPower);
688       }
689       lambdaVDW = lambda;
690       if (useMeld) {
691         lambdaElec = sqrt(meldScaleFactor + lambda * (1.0 - meldScaleFactor));
692         lambdaVDW = meldScaleFactor + lambda * (1.0 - meldScaleFactor);
693       }
694     } else if (vdwLambdaTerm) {
695       // Lambda effects vdW, with electrostatics turned off.
696       lambdaElec = 0.0;
697       lambdaVDW = lambda;
698       if (useMeld) {
699         lambdaVDW = meldScaleFactor + lambda * (1.0 - meldScaleFactor);
700       }
701 
702     } else if (elecLambdaTerm) {
703       // Lambda effects electrostatics, but not vdW.
704       lambdaElec = lambda;
705       if (useMeld) {
706         lambdaElec = sqrt(meldScaleFactor + lambda * (1.0 - meldScaleFactor));
707       }
708     }
709   }
710 
711   /**
712    * Get the value of the vdW lambda term flag.
713    *
714    * @return the vdW lambda term flag.
715    */
716   public boolean getVdwLambdaTerm() {
717     return vdwLambdaTerm;
718   }
719 
720   /**
721    * Set the vdW softcore alpha value.
722    *
723    * @return the vdW softcore alpha value.
724    */
725   public double getVdWSoftcoreAlpha() {
726     return vdWSoftcoreAlpha;
727   }
728 
729   /**
730    * Set the vdW softcore power.
731    *
732    * @return the vdW softcore power.
733    */
734   public double getVdwSoftcorePower() {
735     return vdwSoftcorePower;
736   }
737 
738   public void setUpdateBondedTerms(boolean updateBondedTerms) {
739     this.updateBondedTerms = updateBondedTerms;
740   }
741 
742   /**
743    * Set the default values of the vectors defining the axes of the periodic box (measured in nm).
744    *
745    * <p>Any newly created Context will have its box vectors set to these. They will affect any
746    * Force added to the System that uses periodic boundary conditions.
747    *
748    * <p>Triclinic boxes are supported, but the vectors must satisfy certain requirements. In
749    * particular, a must point in the x direction, b must point "mostly" in the y direction, and c
750    * must point "mostly" in the z direction. See the documentation for details.
751    */
752   private void setDefaultPeriodicBoxVectors() {
753     Crystal crystal = openMMEnergy.getCrystal();
754     if (!crystal.aperiodic()) {
755       OpenMM_Vec3 a = new OpenMM_Vec3();
756       OpenMM_Vec3 b = new OpenMM_Vec3();
757       OpenMM_Vec3 c = new OpenMM_Vec3();
758       double[][] Ai = crystal.Ai;
759       a.x = Ai[0][0] * OpenMM_NmPerAngstrom;
760       a.y = Ai[0][1] * OpenMM_NmPerAngstrom;
761       a.z = Ai[0][2] * OpenMM_NmPerAngstrom;
762       b.x = Ai[1][0] * OpenMM_NmPerAngstrom;
763       b.y = Ai[1][1] * OpenMM_NmPerAngstrom;
764       b.z = Ai[1][2] * OpenMM_NmPerAngstrom;
765       c.x = Ai[2][0] * OpenMM_NmPerAngstrom;
766       c.y = Ai[2][1] * OpenMM_NmPerAngstrom;
767       c.z = Ai[2][2] * OpenMM_NmPerAngstrom;
768       setDefaultPeriodicBoxVectors(a, b, c);
769     }
770   }
771 
772   public double getLambdaElec() {
773     return lambdaElec;
774   }
775 
776   /**
777    * Update parameters if the Use flags and/or Lambda value has changed.
778    *
779    * @param atoms Atoms in this list are considered.
780    */
781   public void updateParameters(@Nullable Atom[] atoms) {
782     if (vdwLambdaTerm) {
783       if (fixedChargeNonBondedForce != null) {
784         if (!softcoreCreated) {
785           fixedChargeAlchemicalForces = new FixedChargeAlchemicalForces(openMMEnergy, fixedChargeNonBondedForce);
786           addForce(fixedChargeAlchemicalForces.getFixedChargeSoftcoreForce());
787           addForce(fixedChargeAlchemicalForces.getAlchemicalAlchemicalStericsForce());
788           addForce(fixedChargeAlchemicalForces.getNonAlchemicalAlchemicalStericsForce());
789           // Re-initialize the context.
790           openMMContext.reinitialize(OpenMM_True);
791           softcoreCreated = true;
792         }
793         openMMContext.setParameter("vdw_lambda", lambdaVDW);
794       } else if (amoebaVDWForce != null) {
795         openMMContext.setParameter("AmoebaVdwLambda", lambdaVDW);
796         if (softcoreCreated) {
797           // Avoid any updateParametersInContext calls if vdwLambdaTerm is true, but not other alchemical terms.
798           if (!torsionLambdaTerm && !elecLambdaTerm) {
799             return;
800           }
801         } else {
802           softcoreCreated = true;
803         }
804       }
805     }
806 
807     // Note Stretch-Torsion and Angle-Torsion terms (for nucleic acids)
808     // and Torsion-Torsion terms (for protein backbones) are not udpated yet.
809     if (updateBondedTerms) {
810       if (bondForce != null) {
811         bondForce.updateForce(openMMEnergy);
812       }
813       if (angleForce != null) {
814         angleForce.updateForce(openMMEnergy);
815       }
816       if (inPlaneAngleForce != null) {
817         inPlaneAngleForce.updateForce(openMMEnergy);
818       }
819       if (stretchBendForce != null) {
820         stretchBendForce.updateForce(openMMEnergy);
821       }
822       if (ureyBradleyForce != null) {
823         ureyBradleyForce.updateForce(openMMEnergy);
824       }
825       if (outOfPlaneBendForce != null) {
826         outOfPlaneBendForce.updateForce(openMMEnergy);
827       }
828       if (piOrbitalTorsionForce != null) {
829         piOrbitalTorsionForce.updateForce(openMMEnergy);
830       }
831     }
832 
833     if (torsionLambdaTerm || updateBondedTerms) {
834       if (torsionForce != null) {
835         torsionForce.setLambdaTorsion(lambdaTorsion);
836         torsionForce.updateForce(openMMEnergy);
837       }
838       if (improperTorsionForce != null) {
839         improperTorsionForce.setLambdaTorsion(lambdaTorsion);
840         improperTorsionForce.updateForce(openMMEnergy);
841       }
842     }
843 
844     if (restrainTorsionsForce != null) {
845       restrainTorsionsForce.updateForce(openMMEnergy);
846     }
847 
848     if (atoms == null || atoms.length == 0) {
849       return;
850     }
851 
852     // Update fixed charge non-bonded parameters.
853     if (fixedChargeNonBondedForce != null) {
854       fixedChargeNonBondedForce.updateForce(atoms, openMMEnergy);
855     }
856 
857     // Update fixed charge GB parameters.
858     if (fixedChargeGBForce != null) {
859       fixedChargeGBForce.updateForce(atoms, openMMEnergy);
860     }
861 
862     // Update AMOEBA vdW parameters.
863     if (amoebaVDWForce != null) {
864       amoebaVDWForce.updateForce(atoms, openMMEnergy);
865     }
866 
867     // Update AMOEBA polarizable multipole parameters.
868     if (amoebaMultipoleForce != null) {
869       amoebaMultipoleForce.updateForce(atoms, openMMEnergy);
870     }
871 
872     // Update GK force.
873     if (amoebaGeneralizedKirkwoodForce != null) {
874       amoebaGeneralizedKirkwoodForce.updateForce(atoms, openMMEnergy);
875     }
876 
877     // Update WCA Force.
878     if (amoebaWcaDispersionForce != null) {
879       amoebaWcaDispersionForce.updateForce(atoms, openMMEnergy);
880     }
881 
882     // Update WCA Force.
883     if (amoebaGKCavitationForce != null) {
884       amoebaGKCavitationForce.updateForce(atoms, openMMEnergy);
885     }
886   }
887 
888   /**
889    * Adds atoms from the molecular assembly to the OpenMM System and reports to the user the number
890    * of particles added.
891    */
892   private void addAtoms() throws Exception {
893     double totalMass = 0.0;
894     for (Atom atom : atoms) {
895       double mass = atom.getMass();
896       totalMass += mass;
897       if (mass < 0.0) {
898         throw new Exception(" Atom with mass less than 0.");
899       }
900       if (mass == 0.0) {
901         logger.info(format(" Atom %s has zero mass.", atom));
902       }
903       addParticle(mass);
904     }
905     logger.log(Level.INFO, format("  Atoms \t\t%6d", atoms.length));
906     logger.log(Level.INFO, format("  Mass  \t\t%12.3f", totalMass));
907   }
908 
909   /**
910    * This method sets the mass of inactive atoms to zero.
911    */
912   public void updateAtomMass() {
913     int index = 0;
914     for (Atom atom : atoms) {
915       double mass = 0.0;
916       if (atom.isActive()) {
917         mass = atom.getMass();
918       }
919       setParticleMass(index++, mass);
920     }
921   }
922 
923   public boolean hasAmoebaCavitationForce() {
924     return amoebaGKCavitationForce != null;
925   }
926 
927   /**
928    * Add a constraint to every bond.
929    */
930   private void addUpBondConstraints() {
931     Bond[] bonds = openMMEnergy.getBonds();
932     if (bonds == null || bonds.length < 1) {
933       return;
934     }
935 
936     logger.info(" Adding constraints for all bonds.");
937     for (Bond bond : bonds) {
938       Atom atom1 = bond.getAtom(0);
939       Atom atom2 = bond.getAtom(1);
940       int iAtom1 = atom1.getXyzIndex() - 1;
941       int iAtom2 = atom2.getXyzIndex() - 1;
942       addConstraint(iAtom1, iAtom2, bond.bondType.distance * OpenMM_NmPerAngstrom);
943     }
944   }
945 
946   /**
947    * Add a constraint to every bond that includes a hydrogen atom.
948    */
949   private void addHydrogenConstraints() {
950     Bond[] bonds = openMMEnergy.getBonds();
951     if (bonds == null || bonds.length < 1) {
952       return;
953     }
954 
955     logger.info(" Adding constraints for hydrogen bonds.");
956     for (Bond bond : bonds) {
957       Atom atom1 = bond.getAtom(0);
958       Atom atom2 = bond.getAtom(1);
959       if (atom1.isHydrogen() || atom2.isHydrogen()) {
960         BondType bondType = bond.bondType;
961         int iAtom1 = atom1.getXyzIndex() - 1;
962         int iAtom2 = atom2.getXyzIndex() - 1;
963         addConstraint(iAtom1, iAtom2, bondType.distance * OpenMM_NmPerAngstrom);
964       }
965     }
966   }
967 
968   /**
969    * Add a constraint to every angle that includes two hydrogen atoms.
970    */
971   private void setUpHydrogenAngleConstraints() {
972     Angle[] angles = openMMEnergy.getAngles();
973     if (angles == null || angles.length < 1) {
974       return;
975     }
976 
977     logger.info(" Adding hydrogen angle constraints.");
978 
979     for (Angle angle : angles) {
980       if (isHydrogenAngle(angle)) {
981         Atom atom1 = angle.getAtom(0);
982         Atom atom3 = angle.getAtom(2);
983 
984         // Calculate a "false bond" length between atoms 1 and 3 to constrain the angle using the
985         // law of cosines.
986         Bond bond1 = angle.getBond(0);
987         double distance1 = bond1.bondType.distance;
988 
989         Bond bond2 = angle.getBond(1);
990         double distance2 = bond2.bondType.distance;
991 
992         // Equilibrium angle value in degrees.
993         double angleVal = angle.angleType.angle[angle.nh];
994 
995         // Law of cosines.
996         double falseBondLength = sqrt(
997             distance1 * distance1 + distance2 * distance2 - 2.0 * distance1 * distance2 * cos(
998                 toRadians(angleVal)));
999 
1000         int iAtom1 = atom1.getXyzIndex() - 1;
1001         int iAtom3 = atom3.getXyzIndex() - 1;
1002         addConstraint(iAtom1, iAtom3, falseBondLength * OpenMM_NmPerAngstrom);
1003       }
1004     }
1005   }
1006 
1007   /**
1008    * Check to see if an angle is a hydrogen angle. This method only returns true for hydrogen
1009    * angles that are less than 160 degrees.
1010    *
1011    * @param angle Angle to check.
1012    * @return boolean indicating whether an angle is a hydrogen angle that is less than 160 degrees.
1013    */
1014   private boolean isHydrogenAngle(Angle angle) {
1015     if (angle.containsHydrogen()) {
1016       // Equilibrium angle value in degrees.
1017       double angleVal = angle.angleType.angle[angle.nh];
1018       // Make sure angle is less than 160 degrees because greater than 160 degrees will not be
1019       // constrained
1020       // well using the law of cosines.
1021       if (angleVal < 160.0) {
1022         Atom atom1 = angle.getAtom(0);
1023         Atom atom2 = angle.getAtom(1);
1024         Atom atom3 = angle.getAtom(2);
1025         // Setting constraints only on angles where atom1 or atom3 is a hydrogen while atom2 is
1026         // not a hydrogen.
1027         return atom1.isHydrogen() && atom3.isHydrogen() && !atom2.isHydrogen();
1028       }
1029     }
1030     return false;
1031   }
1032 
1033 }