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-2025.
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 com.sun.jna.ptr.DoubleByReference;
41  import com.sun.jna.ptr.IntByReference;
42  import ffx.crystal.Crystal;
43  import ffx.openmm.BondArray;
44  import ffx.openmm.Force;
45  import ffx.openmm.NonbondedForce;
46  import ffx.potential.bonded.Atom;
47  import ffx.potential.bonded.Bond;
48  import ffx.potential.nonbonded.NonbondedCutoff;
49  import ffx.potential.nonbonded.ParticleMeshEwald;
50  import ffx.potential.nonbonded.VanDerWaals;
51  import ffx.potential.nonbonded.VanDerWaalsForm;
52  import ffx.potential.parameters.ForceField;
53  import ffx.potential.parameters.MultipoleType;
54  import ffx.potential.parameters.VDWType;
55  
56  import java.util.logging.Level;
57  import java.util.logging.Logger;
58  
59  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AngstromsPerNm;
60  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_KJPerKcal;
61  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
62  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_False;
63  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_True;
64  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_NonbondedForce_NonbondedMethod.OpenMM_NonbondedForce_NoCutoff;
65  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_NonbondedForce_NonbondedMethod.OpenMM_NonbondedForce_PME;
66  import static ffx.potential.parameters.VDWType.EPSILON_RULE.GEOMETRIC;
67  import static ffx.potential.parameters.VDWType.RADIUS_RULE.ARITHMETIC;
68  import static ffx.potential.parameters.VDWType.RADIUS_SIZE.RADIUS;
69  import static ffx.potential.parameters.VDWType.RADIUS_TYPE.R_MIN;
70  import static ffx.potential.parameters.VDWType.VDW_TYPE.LENNARD_JONES;
71  import static java.lang.String.format;
72  import static org.apache.commons.math3.util.FastMath.abs;
73  
74  /**
75   * Define a fixed charge non-bonded force.
76   * <p>
77   * Uses arithmetic mean to define sigma and geometric mean for epsilon.
78   */
79  public class FixedChargeNonbondedForce extends NonbondedForce {
80  
81    private static final Logger logger = Logger.getLogger(FixedChargeNonbondedForce.class.getName());
82  
83    /**
84     * Boolean array, holds charge exclusion list.
85     */
86    private boolean[] chargeExclusion;
87    /**
88     * Boolean array, holds van Der Waals exclusion list.
89     */
90    private boolean[] vdWExclusion;
91    /**
92     * Double array, holds charge quantity value for exceptions.
93     */
94    private double[] exceptionChargeProd;
95    /**
96     * Double array, holds epsilon quantity value for exceptions.
97     */
98    private double[] exceptionEps;
99  
100   public FixedChargeNonbondedForce(OpenMMEnergy openMMEnergy) {
101     VanDerWaals vdW = openMMEnergy.getVdwNode();
102     if (vdW == null) {
103       // Free the memory created by the OpenMMNonbondedForce constructor.
104       destroy();
105       return;
106     }
107 
108     /*
109      Only 6-12 LJ with arithmetic mean to define sigma and geometric mean
110      for epsilon is supported.
111     */
112     VanDerWaalsForm vdwForm = vdW.getVDWForm();
113     if (vdwForm.vdwType != LENNARD_JONES || vdwForm.radiusRule != ARITHMETIC
114         || vdwForm.epsilonRule != GEOMETRIC) {
115       logger.info(format(" VDW Type:         %s", vdwForm.vdwType));
116       logger.info(format(" VDW Radius Rule:  %s", vdwForm.radiusRule));
117       logger.info(format(" VDW Epsilon Rule: %s", vdwForm.epsilonRule));
118       logger.log(Level.SEVERE, " Unsupported van der Waals functional form.");
119       return;
120     }
121 
122     // OpenMM vdW force requires a diameter (i.e. not radius).
123     double radScale = 1.0;
124     if (vdwForm.radiusSize == RADIUS) {
125       radScale = 2.0;
126     }
127 
128     // OpenMM vdw force requires atomic sigma values (i.e. not r-min).
129     if (vdwForm.radiusType == R_MIN) {
130       radScale /= 1.122462048309372981;
131     }
132 
133     // Add particles.
134     Atom[] atoms = openMMEnergy.getMolecularAssembly().getAtomArray();
135     for (Atom atom : atoms) {
136       VDWType vdwType = atom.getVDWType();
137       double sigma = OpenMM_NmPerAngstrom * vdwType.radius * radScale;
138       double eps = OpenMM_KJPerKcal * vdwType.wellDepth;
139       double charge = 0.0;
140       MultipoleType multipoleType = atom.getMultipoleType();
141       if (multipoleType != null && atom.getElectrostatics()) {
142         charge = multipoleType.charge;
143       }
144       addParticle(charge, sigma, eps);
145     }
146 
147     // Define 1-4 scale factors.
148     double lj14Scale = vdwForm.getScale14();
149     double coulomb14Scale = 1.0 / 1.2;
150     ParticleMeshEwald pme = openMMEnergy.getPmeNode();
151     if (pme != null) {
152       coulomb14Scale = pme.getScale14();
153     }
154 
155     Bond[] bonds = openMMEnergy.getBonds();
156     if (bonds != null && bonds.length > 0) {
157       BondArray bondArray = new BondArray(0);
158       for (Bond bond : bonds) {
159         int i1 = bond.getAtom(0).getXyzIndex() - 1;
160         int i2 = bond.getAtom(1).getXyzIndex() - 1;
161         bondArray.append(i1, i2);
162       }
163       createExceptionsFromBonds(bondArray, coulomb14Scale, lj14Scale);
164       bondArray.destroy();
165 
166       int num = getNumExceptions();
167       chargeExclusion = new boolean[num];
168       vdWExclusion = new boolean[num];
169       exceptionChargeProd = new double[num];
170       exceptionEps = new double[num];
171       IntByReference particle1 = new IntByReference();
172       IntByReference particle2 = new IntByReference();
173       DoubleByReference chargeProd = new DoubleByReference();
174       DoubleByReference sigma = new DoubleByReference();
175       DoubleByReference eps = new DoubleByReference();
176       for (int i = 0; i < num; i++) {
177         getExceptionParameters(i, particle1, particle2, chargeProd, sigma, eps);
178         if (abs(chargeProd.getValue()) > 0.0) {
179           chargeExclusion[i] = false;
180           exceptionChargeProd[i] = chargeProd.getValue();
181         } else {
182           exceptionChargeProd[i] = 0.0;
183           chargeExclusion[i] = true;
184         }
185         if (abs(eps.getValue()) > 0.0) {
186           vdWExclusion[i] = false;
187           exceptionEps[i] = eps.getValue();
188         } else {
189           vdWExclusion[i] = true;
190           exceptionEps[i] = 0.0;
191         }
192       }
193     }
194 
195     Crystal crystal = openMMEnergy.getCrystal();
196     if (crystal.aperiodic()) {
197       setNonbondedMethod(OpenMM_NonbondedForce_NoCutoff);
198     } else {
199       setNonbondedMethod(OpenMM_NonbondedForce_PME);
200       if (pme != null) {
201         // Units of the Ewald coefficient are A^-1; Multiply by AngstromsPerNM to convert to (Nm^-1).
202         double aEwald = OpenMM_AngstromsPerNm * pme.getEwaldCoefficient();
203         int nx = pme.getReciprocalSpace().getXDim();
204         int ny = pme.getReciprocalSpace().getYDim();
205         int nz = pme.getReciprocalSpace().getZDim();
206         setPMEParameters(aEwald, nx, ny, nz);
207       }
208 
209       NonbondedCutoff nonbondedCutoff = vdW.getNonbondedCutoff();
210       double off = nonbondedCutoff.off;
211       double cut = nonbondedCutoff.cut;
212       setCutoffDistance(OpenMM_NmPerAngstrom * off);
213       setUseSwitchingFunction(OpenMM_True);
214       if (cut == off) {
215         logger.warning(" OpenMM does not properly handle cutoffs where cut == off!");
216         if (cut == Double.MAX_VALUE || cut == Double.POSITIVE_INFINITY) {
217           logger.info(" Detected infinite or max-value cutoff; setting cut to 1E+40 for OpenMM.");
218           cut = 1E40;
219         } else {
220           logger.info(format(" Detected cut %8.4g == off %8.4g; scaling cut to 0.99 of off for OpenMM.", cut, off));
221           cut *= 0.99;
222         }
223       }
224       setSwitchingDistance(OpenMM_NmPerAngstrom * cut);
225     }
226 
227     setUseDispersionCorrection(OpenMM_False);
228 
229     ForceField forceField = openMMEnergy.getMolecularAssembly().getForceField();
230     int forceGroup = forceField.getInteger("VDW_FORCE_GROUP", 1);
231     int pmeGroup = forceField.getInteger("PME_FORCE_GROUP", 1);
232     if (forceGroup != pmeGroup) {
233       logger.severe(format(" ERROR: VDW-FORCE-GROUP is %d while PME-FORCE-GROUP is %d. "
234               + "This is invalid for fixed-charge force fields with combined non-bonded forces.",
235           forceGroup, pmeGroup));
236     }
237 
238     setForceGroup(forceGroup);
239     logger.log(Level.INFO, format("  Fixed charge non-bonded force \t%1d", forceGroup));
240   }
241 
242   /**
243    * Convenience method to construct an OpenMM Non-Bonded Force.
244    *
245    * @param openMMEnergy The OpenMM Energy instance that contains the Non-Bonded Force information.
246    * @return An OpenMM Non-Bonded Force, or null if there is no vdW information.
247    */
248   public static Force constructForce(OpenMMEnergy openMMEnergy) {
249     VanDerWaals vdW = openMMEnergy.getVdwNode();
250     if (vdW == null) {
251       // Free the memory created by the OpenMMNonbondedForce constructor.
252       return null;
253     }
254 
255     /*
256      Only 6-12 LJ with arithmetic mean to define sigma and geometric mean
257      for epsilon is supported.
258     */
259     VanDerWaalsForm vdwForm = vdW.getVDWForm();
260     if (vdwForm.vdwType != LENNARD_JONES || vdwForm.radiusRule != ARITHMETIC
261         || vdwForm.epsilonRule != GEOMETRIC) {
262       logger.info(format(" VDW Type:         %s", vdwForm.vdwType));
263       logger.info(format(" VDW Radius Rule:  %s", vdwForm.radiusRule));
264       logger.info(format(" VDW Epsilon Rule: %s", vdwForm.epsilonRule));
265       logger.log(Level.SEVERE, " Unsupported van der Waals functional form.");
266       return null;
267     }
268 
269     return new FixedChargeNonbondedForce(openMMEnergy);
270   }
271 
272   /**
273    * Update an existing non-bonded force for the OpenMM System.
274    *
275    * @param atoms        The Atom array.
276    * @param openMMEnergy The OpenMM Energy instance that contains the non-bonded force information.
277    */
278   public void updateForce(Atom[] atoms, OpenMMEnergy openMMEnergy) {
279     VanDerWaals vdW = openMMEnergy.getVdwNode();
280     // Only 6-12 LJ with arithmetic mean to define sigma and geometric mean for epsilon is
281     // supported.
282     VanDerWaalsForm vdwForm = vdW.getVDWForm();
283     if (vdwForm.vdwType != LENNARD_JONES || vdwForm.radiusRule != ARITHMETIC
284         || vdwForm.epsilonRule != GEOMETRIC) {
285       logger.log(Level.SEVERE, " Unsupported van der Waals functional form.");
286       return;
287     }
288 
289     // OpenMM vdW force requires a diameter (i.e. not radius).
290     double radScale = 1.0;
291     if (vdwForm.radiusSize == RADIUS) {
292       radScale = 2.0;
293     }
294 
295     // OpenMM vdw force requires atomic sigma values (i.e. not r-min).
296     if (vdwForm.radiusType == R_MIN) {
297       radScale /= 1.122462048309372981;
298     }
299 
300     // Update parameters.
301     for (Atom atom : atoms) {
302       int index = atom.getXyzIndex() - 1;
303       boolean applyLambda = atom.applyLambda();
304 
305       double charge = Double.MIN_VALUE;
306       MultipoleType multipoleType = atom.getMultipoleType();
307       if (multipoleType != null && atom.getElectrostatics()) {
308         charge = multipoleType.charge;
309       }
310 
311       VDWType vdwType = atom.getVDWType();
312       double sigma = OpenMM_NmPerAngstrom * vdwType.radius * radScale;
313       double eps = OpenMM_KJPerKcal * vdwType.wellDepth;
314 
315       if (applyLambda) {
316         boolean vdwLambdaTerm = vdW.getLambdaTerm();
317         // If we're using vdwLambdaTerm, this atom's vdW interactions are handled by the Custom Non-Bonded force.
318         if (vdwLambdaTerm) {
319           eps = 0.0;
320         }
321         // Always scale the charge by lambdaElec
322         double permLambda = openMMEnergy.getPmeNode().getAlchemicalParameters().permLambda;
323         charge *= permLambda;
324       }
325 
326       if (!atom.getUse()) {
327         eps = 0.0;
328         charge = 0.0;
329       }
330 
331       setParticleParameters(index, charge, sigma, eps);
332     }
333 
334     // Update Exceptions.
335     IntByReference particle1 = new IntByReference();
336     IntByReference particle2 = new IntByReference();
337     DoubleByReference chargeProd = new DoubleByReference();
338     DoubleByReference sigma = new DoubleByReference();
339     DoubleByReference eps = new DoubleByReference();
340 
341     int numExceptions = getNumExceptions();
342     for (int i = 0; i < numExceptions; i++) {
343 
344       // Only update exceptions.
345       if (chargeExclusion[i] && vdWExclusion[i]) {
346         continue;
347       }
348 
349       getExceptionParameters(i, particle1, particle2, chargeProd, sigma, eps);
350 
351       int i1 = particle1.getValue();
352       int i2 = particle2.getValue();
353 
354       double qq = exceptionChargeProd[i];
355       double epsilon = exceptionEps[i];
356 
357       Atom atom1 = atoms[i1];
358       Atom atom2 = atoms[i2];
359 
360       /*
361       Note that the minimum epsilon value cannot be zero, or OpenMM may
362       report an error that the number of Exceptions has changed.
363       */
364       double minEpsilon = 1.0e-12;
365 
366       double lambdaElec = 1.0;
367       ParticleMeshEwald pme = openMMEnergy.getPmeNode();
368       if (pme != null) {
369         lambdaElec = pme.getAlchemicalParameters().permLambda;
370       }
371 
372       boolean vdwLambdaTerm = vdW.getLambdaTerm();
373 
374       if (lambdaElec < minEpsilon) {
375         lambdaElec = minEpsilon;
376       }
377 
378       if (atom1.applyLambda()) {
379         qq *= lambdaElec;
380         if (vdwLambdaTerm) {
381           epsilon = minEpsilon;
382         }
383       }
384       if (atom2.applyLambda()) {
385         qq *= lambdaElec;
386         if (vdwLambdaTerm) {
387           epsilon = minEpsilon;
388         }
389       }
390       if (!atom1.getUse() || !atom2.getUse()) {
391         qq = minEpsilon;
392         epsilon = minEpsilon;
393       }
394 
395       setExceptionParameters(i, i1, i2, qq, sigma.getValue(), epsilon);
396     }
397 
398     // Update the parameters in the OpenMM Context.
399     updateParametersInContext(openMMEnergy.getContext());
400   }
401 
402 }