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