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-2026.
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 ffx.crystal.Crystal;
41  import ffx.openmm.Force;
42  import ffx.openmm.IntArray;
43  import ffx.openmm.amoeba.VdwForce;
44  import ffx.potential.ForceFieldEnergy;
45  import ffx.potential.bonded.Atom;
46  import ffx.potential.extended.ExtendedSystem;
47  import ffx.potential.nonbonded.NonbondedCutoff;
48  import ffx.potential.nonbonded.VanDerWaals;
49  import ffx.potential.nonbonded.VanDerWaalsForm;
50  import ffx.potential.parameters.ForceField;
51  import ffx.potential.parameters.VDWPairType;
52  import ffx.potential.parameters.VDWType;
53  
54  import java.util.HashMap;
55  import java.util.Map;
56  import java.util.logging.Level;
57  import java.util.logging.Logger;
58  
59  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaVdwForce_AlchemicalMethod.OpenMM_AmoebaVdwForce_Annihilate;
60  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaVdwForce_AlchemicalMethod.OpenMM_AmoebaVdwForce_Decouple;
61  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaVdwForce_NonbondedMethod.OpenMM_AmoebaVdwForce_CutoffPeriodic;
62  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaVdwForce_NonbondedMethod.OpenMM_AmoebaVdwForce_NoCutoff;
63  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_KJPerKcal;
64  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
65  import static java.lang.Math.sqrt;
66  import static java.lang.String.format;
67  
68  /**
69   * The Amoeba vdW Force.
70   */
71  public class AmoebaVdwForce extends VdwForce {
72  
73    private static final Logger logger = Logger.getLogger(AmoebaVdwForce.class.getName());
74  
75    /**
76     * The vdW class used to specify no vdW interactions for an atom will be Zero
77     * if all atom classes are greater than zero.
78     * <p>
79     * Otherwise:
80     * vdWClassForNoInteraction = min(atomClass) - 1
81     */
82    private int vdWClassForNoInteraction = 0;
83  
84    /**
85     * A map from vdW class values to OpenMM vdW types.
86     */
87    private final Map<Integer, Integer> vdwClassToOpenMMType = new HashMap<>();
88  
89    /**
90     * The Amoeba vdW Force constructor.
91     *
92     * @param openMMEnergy The OpenMM Energy instance that contains the vdW parameters.
93     */
94    public AmoebaVdwForce(OpenMMEnergy openMMEnergy) {
95      VanDerWaals vdW = openMMEnergy.getVdwNode();
96      if (vdW == null) {
97        destroy();
98        return;
99      }
100 
101     // Configure the Amoeba vdW Force.
102     configureForce(openMMEnergy);
103 
104     // Add the particles.
105     ExtendedSystem extendedSystem = vdW.getExtendedSystem();
106     double[] vdwPrefactorAndDerivs = new double[3];
107 
108     int[] ired = vdW.getReductionIndex();
109     Atom[] atoms = openMMEnergy.getMolecularAssembly().getAtomArray();
110     int nAtoms = atoms.length;
111     for (int i = 0; i < nAtoms; i++) {
112       Atom atom = atoms[i];
113       VDWType vdwType = atom.getVDWType();
114       int atomClass = vdwType.atomClass;
115       int type = vdwClassToOpenMMType.get(atomClass);
116       int isAlchemical = atom.applyLambda() ? 1 : 0;
117       double scaleFactor = 1.0;
118       if (extendedSystem != null) {
119         extendedSystem.getVdwPrefactor(i, vdwPrefactorAndDerivs);
120         scaleFactor = vdwPrefactorAndDerivs[0];
121       }
122       addParticle(ired[i], type, vdwType.reductionFactor, isAlchemical, scaleFactor);
123     }
124 
125     // Create exclusion lists.
126     int[][] bondMask = vdW.getMask12();
127     int[][] angleMask = vdW.getMask13();
128     IntArray exclusions = new IntArray(0);
129     for (int i = 0; i < nAtoms; i++) {
130       exclusions.append(i);
131       final int[] bondMaski = bondMask[i];
132       for (int value : bondMaski) {
133         exclusions.append(value);
134       }
135       final int[] angleMaski = angleMask[i];
136       for (int value : angleMaski) {
137         exclusions.append(value);
138       }
139       setParticleExclusions(i, exclusions);
140       exclusions.resize(0);
141     }
142     exclusions.destroy();
143   }
144 
145   /**
146    * The Amoeba vdW Force constructor used for dual-topology simulations.
147    *
148    * @param topology                 The topology index for the dual topology system.
149    * @param openMMDualTopologyEnergy The OpenMM Energy instance that contains the vdW parameters.
150    */
151   public AmoebaVdwForce(int topology, OpenMMDualTopologyEnergy openMMDualTopologyEnergy) {
152 
153     ForceFieldEnergy forceFieldEnergy = openMMDualTopologyEnergy.getForceFieldEnergy(topology);
154     VanDerWaals vdW = forceFieldEnergy.getVdwNode();
155     if (vdW == null) {
156       destroy();
157       return;
158     }
159 
160     double scale = sqrt(openMMDualTopologyEnergy.getTopologyScale(topology));
161 
162     // Configure the Amoeba vdW Force.
163     configureForce(forceFieldEnergy);
164 
165     // Add the particles.
166     ExtendedSystem extendedSystem = vdW.getExtendedSystem();
167     if (extendedSystem != null) {
168       logger.severe(" Extended system is not supported for dual-topology simulations.");
169     }
170 
171     int nAtoms = openMMDualTopologyEnergy.getNumberOfAtoms();
172 
173     int[] ired = vdW.getReductionIndex();
174 
175     // Add a particle for each atom in the dual topology.
176     for (int i = 0; i < nAtoms; i++) {
177       Atom atom = openMMDualTopologyEnergy.getDualTopologyAtom(topology, i);
178       int top = atom.getTopologyIndex();
179       if (top == topology) {
180         int index = atom.getArrayIndex();
181         int ir = openMMDualTopologyEnergy.mapToDualTopologyIndex(topology, ired[index]);
182         VDWType vdwType = atom.getVDWType();
183         int atomClass = vdwType.atomClass;
184         int type = vdwClassToOpenMMType.get(atomClass);
185         int isAlchemical = atom.applyLambda() ? 1 : 0;
186         addParticle(ir, type, vdwType.reductionFactor, isAlchemical, scale);
187       } else {
188         // Add a fake particle for an atom not in this topology.
189         int index = atom.getTopologyAtomIndex();
190         int type = vdwClassToOpenMMType.get(vdWClassForNoInteraction);
191         int isAlchemical = 1;
192         double scaleFactor = 0.0;
193         addParticle(index, type, 1.0, isAlchemical, scaleFactor);
194       }
195     }
196 
197     // Create exclusion lists only for the atoms in this topology.
198     int[][] bondMask = vdW.getMask12();
199     int[][] angleMask = vdW.getMask13();
200     IntArray exclusions = new IntArray(0);
201     for (int index = 0; index < nAtoms; index++) {
202       Atom atom = openMMDualTopologyEnergy.getDualTopologyAtom(topology, index);
203       if (atom.getTopologyIndex() != topology) {
204         continue; // Skip atoms not in this topology.
205       }
206       exclusions.append(index);
207 
208       // Exclude 1-2 interactions. The bond mask uses single topology indices and
209       // must be mapped to the dual topology index.
210       final int[] bondMaski = bondMask[atom.getArrayIndex()];
211       for (int value : bondMaski) {
212         // Map to the dual topology index.
213         value = openMMDualTopologyEnergy.mapToDualTopologyIndex(topology, value);
214         exclusions.append(value);
215       }
216 
217       // Exclude 1-3 interactions. The angle mask uses single topology indices and
218       // must be mapped to the dual topology index.
219       final int[] angleMaski = angleMask[atom.getArrayIndex()];
220       for (int value : angleMaski) {
221         // Map to the dual topology index.
222         value = openMMDualTopologyEnergy.mapToDualTopologyIndex(topology, value);
223         exclusions.append(value);
224       }
225       setParticleExclusions(index, exclusions);
226 
227       // Reset the exclusions for the next atom.
228       exclusions.resize(0);
229     }
230     exclusions.destroy();
231   }
232 
233   /**
234    * Configuration of the AMOEBA vdW force that is used for both single and dual-topology simulations.
235    *
236    * @param forceFieldEnergy The ForceFieldEnergy instance that contains the vdW parameters.
237    */
238   private void configureForce(ForceFieldEnergy forceFieldEnergy) {
239     VanDerWaals vdW = forceFieldEnergy.getVdwNode();
240     VanDerWaalsForm vdwForm = vdW.getVDWForm();
241 
242     double radScale = 1.0;
243     if (vdwForm.radiusSize == VDWType.RADIUS_SIZE.DIAMETER) {
244       radScale = 0.5;
245     }
246 
247     ForceField forceField = forceFieldEnergy.getMolecularAssembly().getForceField();
248     Map<String, VDWType> vdwTypes = forceField.getVDWTypes();
249     for (VDWType vdwType : vdwTypes.values()) {
250       int atomClass = vdwType.atomClass;
251       if (!vdwClassToOpenMMType.containsKey(atomClass)) {
252         double eps = OpenMM_KJPerKcal * vdwType.wellDepth;
253         double rad = OpenMM_NmPerAngstrom * vdwType.radius * radScale;
254         // OpenMM AMOEBA vdW class does not allow a radius of 0.
255         if (rad == 0) {
256           rad = OpenMM_NmPerAngstrom * radScale;
257         }
258         int type = addParticleType(rad, eps);
259         vdwClassToOpenMMType.put(atomClass, type);
260         if (atomClass <= vdWClassForNoInteraction) {
261           vdWClassForNoInteraction = atomClass - 1;
262         }
263       }
264     }
265 
266     // Add a special vdW type for zero vdW energy and forces (e.g. to support the FFX "use" flag).
267     int type = addParticleType(OpenMM_NmPerAngstrom, 0.0);
268     vdwClassToOpenMMType.put(vdWClassForNoInteraction, type);
269 
270     Map<String, VDWPairType> vdwPairTypeMap = forceField.getVDWPairTypes();
271     for (VDWPairType vdwPairType : vdwPairTypeMap.values()) {
272       int c1 = vdwPairType.atomClasses[0];
273       int c2 = vdwPairType.atomClasses[1];
274       int type1 = vdwClassToOpenMMType.get(c1);
275       int type2 = vdwClassToOpenMMType.get(c2);
276       double rMin = vdwPairType.radius * OpenMM_NmPerAngstrom;
277       double eps = vdwPairType.wellDepth * OpenMM_KJPerKcal;
278       addTypePair(type1, type2, rMin, eps);
279       addTypePair(type2, type1, rMin, eps);
280     }
281 
282     // Set the nonbonded cutoff and dispersion correction.
283     NonbondedCutoff nonbondedCutoff = vdW.getNonbondedCutoff();
284     setCutoffDistance(nonbondedCutoff.off * OpenMM_NmPerAngstrom);
285     if (vdW.getDoLongRangeCorrection()) {
286       setUseDispersionCorrection(true);
287     } else {
288       setUseDispersionCorrection(false);
289     }
290 
291     // Set the nonbonded method based on the crystal periodicity.
292     Crystal crystal = forceFieldEnergy.getCrystal();
293     if (crystal.aperiodic()) {
294       setNonbondedMethod(OpenMM_AmoebaVdwForce_NoCutoff);
295     } else {
296       setNonbondedMethod(OpenMM_AmoebaVdwForce_CutoffPeriodic);
297     }
298 
299     // Set the alchemical method if the vdW force has a lambda term.
300     if (vdW.getLambdaTerm()) {
301       boolean annihilate = vdW.getIntramolecularSoftcore();
302       if (annihilate) {
303         setAlchemicalMethod(OpenMM_AmoebaVdwForce_Annihilate);
304       } else {
305         setAlchemicalMethod(OpenMM_AmoebaVdwForce_Decouple);
306       }
307       setSoftcoreAlpha(vdW.getAlpha());
308       setSoftcorePower((int) vdW.getBeta());
309     }
310 
311     int forceGroup = forceField.getInteger("VDW_FORCE_GROUP", 1);
312     setForceGroup(forceGroup);
313 
314     logger.log(Level.INFO, vdW.toString());
315     logger.log(Level.FINE, format("   Force group:\t\t%d\n", forceGroup));
316   }
317 
318   /**
319    * Convenience method to construct an AMOEBA vdW force.
320    *
321    * @param openMMEnergy The OpenMM Energy instance that contains the vdW information.
322    * @return An AMOEBA vdW Force, or null if there are no vdW interactions.
323    */
324   public static Force constructForce(OpenMMEnergy openMMEnergy) {
325     VanDerWaals vdW = openMMEnergy.getVdwNode();
326     if (vdW == null) {
327       return null;
328     }
329     return new AmoebaVdwForce(openMMEnergy);
330   }
331 
332   /**
333    * Convenience method to construct an AMOEBA vdW force for a dual-topology simulation.
334    *
335    * @param topology                 The topology index for the dual topology system.
336    * @param openMMDualTopologyEnergy The OpenMM Dual-Topology Energy instance that contains the vdW information.
337    * @return An AMOEBA vdW Force, or null if there are no vdW interactions.
338    */
339   public static Force constructForce(int topology, OpenMMDualTopologyEnergy openMMDualTopologyEnergy) {
340     ForceFieldEnergy forceFieldEnergy = openMMDualTopologyEnergy.getForceFieldEnergy(topology);
341     VanDerWaals vdW = forceFieldEnergy.getVdwNode();
342     if (vdW == null) {
343       return null;
344     }
345     return new AmoebaVdwForce(topology, openMMDualTopologyEnergy);
346   }
347 
348   /**
349    * Update the vdW force.
350    *
351    * @param atoms        The atoms to update.
352    * @param openMMEnergy The OpenMM Energy instance that contains the vdW parameters.
353    */
354   public void updateForce(Atom[] atoms, OpenMMEnergy openMMEnergy) {
355     VanDerWaals vdW = openMMEnergy.getVdwNode();
356     VanDerWaalsForm vdwForm = vdW.getVDWForm();
357     double radScale = 1.0;
358     if (vdwForm.radiusSize == VDWType.RADIUS_SIZE.DIAMETER) {
359       radScale = 0.5;
360     }
361 
362     ExtendedSystem extendedSystem = vdW.getExtendedSystem();
363     double[] vdwPrefactorAndDerivs = new double[3];
364 
365     int[] ired = vdW.getReductionIndex();
366     for (Atom atom : atoms) {
367       int index = atom.getArrayIndex();
368       VDWType vdwType = atom.getVDWType();
369 
370       // Get the OpenMM index for this vdW type.
371       int type = vdwClassToOpenMMType.get(vdwType.atomClass);
372       if (!atom.getUse()) {
373         // Get the OpenMM index for a special vdW type that has no interactions.
374         type = vdwClassToOpenMMType.get(vdWClassForNoInteraction);
375       }
376       int isAlchemical = atom.applyLambda() ? 1 : 0;
377       double eps = OpenMM_KJPerKcal * vdwType.wellDepth;
378       double rad = OpenMM_NmPerAngstrom * vdwType.radius * radScale;
379 
380       double scaleFactor = 1.0;
381       if (extendedSystem != null) {
382         extendedSystem.getVdwPrefactor(index, vdwPrefactorAndDerivs);
383         scaleFactor = vdwPrefactorAndDerivs[0];
384       }
385 
386       setParticleParameters(index, ired[index], rad, eps, vdwType.reductionFactor, isAlchemical, type, scaleFactor);
387     }
388     updateParametersInContext(openMMEnergy.getContext());
389   }
390 
391   /**
392    * Update the vdW force.
393    *
394    * @param atoms                    The atoms to update.
395    * @param topology                 The topology index for the dual topology system.
396    * @param openMMDualTopologyEnergy The OpenMM Dual-Topology Energy instance that contains the vdW parameters.
397    */
398   public void updateForce(Atom[] atoms, int topology, OpenMMDualTopologyEnergy openMMDualTopologyEnergy) {
399     ForceFieldEnergy forceFieldEnergy = openMMDualTopologyEnergy.getForceFieldEnergy(topology);
400     double scale = sqrt(openMMDualTopologyEnergy.getTopologyScale(topology));
401 
402     VanDerWaals vdW = forceFieldEnergy.getVdwNode();
403     VanDerWaalsForm vdwForm = vdW.getVDWForm();
404     double radScale = 1.0;
405     if (vdwForm.radiusSize == VDWType.RADIUS_SIZE.DIAMETER) {
406       radScale = 0.5;
407     }
408 
409     // Remap the reduction index to the dual topology index.
410     int[] ired = vdW.getReductionIndex();
411 
412     for (Atom atom : atoms) {
413       if (atom.getTopologyIndex() != topology) {
414         // Skip atoms not in this topology.
415         continue;
416       }
417 
418       // Get the dual topology index for this atom.
419       int indexDT = atom.getTopologyAtomIndex();
420       // Map the reduction index for this atom from single to dual topology.
421       int ir = ired[atom.getArrayIndex()];
422       ir = openMMDualTopologyEnergy.mapToDualTopologyIndex(topology, ir);
423 
424       VDWType vdwType = atom.getVDWType();
425       // Get the OpenMM index for this vdW type.
426       int type = vdwClassToOpenMMType.get(vdwType.atomClass);
427       if (!atom.getUse()) {
428         // Get the OpenMM index for a special vdW type that has no interactions.
429         type = vdwClassToOpenMMType.get(vdWClassForNoInteraction);
430       }
431       int isAlchemical = atom.applyLambda() ? 1 : 0;
432       double eps = OpenMM_KJPerKcal * vdwType.wellDepth;
433       double rad = OpenMM_NmPerAngstrom * vdwType.radius * radScale;
434       setParticleParameters(indexDT, ir, rad, eps, vdwType.reductionFactor, isAlchemical, type, scale);
435     }
436     updateParametersInContext(openMMDualTopologyEnergy.getContext());
437   }
438 
439 }