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 ffx.crystal.Crystal;
41  import ffx.openmm.DoubleArray;
42  import ffx.openmm.Force;
43  import ffx.openmm.IntArray;
44  import ffx.openmm.amoeba.MultipoleForce;
45  import ffx.potential.bonded.Atom;
46  import ffx.potential.nonbonded.ParticleMeshEwald;
47  import ffx.potential.nonbonded.ReciprocalSpace;
48  import ffx.potential.nonbonded.pme.Polarization;
49  import ffx.potential.nonbonded.pme.SCFAlgorithm;
50  import ffx.potential.parameters.ForceField;
51  import ffx.potential.parameters.MultipoleType;
52  import ffx.potential.parameters.PolarizeType;
53  
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  
57  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_CovalentType.OpenMM_AmoebaMultipoleForce_Covalent12;
58  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_CovalentType.OpenMM_AmoebaMultipoleForce_Covalent13;
59  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_CovalentType.OpenMM_AmoebaMultipoleForce_Covalent14;
60  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_CovalentType.OpenMM_AmoebaMultipoleForce_Covalent15;
61  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_CovalentType.OpenMM_AmoebaMultipoleForce_PolarizationCovalent11;
62  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_Bisector;
63  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_NoAxisType;
64  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_ThreeFold;
65  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_ZBisect;
66  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_ZOnly;
67  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_MultipoleAxisTypes.OpenMM_AmoebaMultipoleForce_ZThenX;
68  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_NonbondedMethod.OpenMM_AmoebaMultipoleForce_NoCutoff;
69  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_NonbondedMethod.OpenMM_AmoebaMultipoleForce_PME;
70  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_PolarizationType.OpenMM_AmoebaMultipoleForce_Direct;
71  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_PolarizationType.OpenMM_AmoebaMultipoleForce_Extrapolated;
72  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AmoebaMultipoleForce_PolarizationType.OpenMM_AmoebaMultipoleForce_Mutual;
73  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
74  import static java.lang.String.format;
75  import static org.apache.commons.math3.util.FastMath.sqrt;
76  
77  /**
78   * AmoebaMultipoleForce.
79   */
80  public class AmoebaMultipoleForce extends MultipoleForce {
81  
82    private static final Logger logger = Logger.getLogger(AmoebaMultipoleForce.class.getName());
83  
84    public AmoebaMultipoleForce(OpenMMEnergy openMMEnergy) {
85      ParticleMeshEwald pme = openMMEnergy.getPmeNode();
86      if (pme == null) {
87        destroy();
88        return;
89      }
90  
91      int[][] axisAtom = pme.getAxisAtoms();
92      double quadrupoleConversion = OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom;
93      double polarityConversion = OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom;
94      double dampingFactorConversion = sqrt(OpenMM_NmPerAngstrom);
95      double polarScale = 1.0;
96      SCFAlgorithm scfAlgorithm = null;
97  
98      ForceField forceField = openMMEnergy.getMolecularAssembly().getForceField();
99  
100     if (pme.getPolarizationType() != Polarization.MUTUAL) {
101       setPolarizationType(OpenMM_AmoebaMultipoleForce_Direct);
102       if (pme.getPolarizationType() == Polarization.NONE) {
103         polarScale = 0.0;
104       }
105     } else {
106       String algorithm = forceField.getString("SCF_ALGORITHM", "CG");
107       try {
108         algorithm = algorithm.replaceAll("-", "_").toUpperCase();
109         scfAlgorithm = SCFAlgorithm.valueOf(algorithm);
110       } catch (Exception e) {
111         scfAlgorithm = SCFAlgorithm.CG;
112       }
113 
114       if (scfAlgorithm == SCFAlgorithm.EPT) {
115         /*
116          * Citation:
117          * Simmonett, A. C.;  Pickard, F. C. t.;  Shao, Y.;  Cheatham, T. E., 3rd; Brooks, B. R.,
118          * Efficient treatment of induced dipoles. The Journal of chemical physics 2015, 143 (7), 074115-074115.
119          */
120         setPolarizationType(OpenMM_AmoebaMultipoleForce_Extrapolated);
121         DoubleArray exptCoefficients = new DoubleArray(4);
122         exptCoefficients.set(0, -0.154);
123         exptCoefficients.set(1, 0.017);
124         exptCoefficients.set(2, 0.657);
125         exptCoefficients.set(3, 0.475);
126         setExtrapolationCoefficients(exptCoefficients);
127         exptCoefficients.destroy();
128       } else {
129         setPolarizationType(OpenMM_AmoebaMultipoleForce_Mutual);
130       }
131     }
132 
133     DoubleArray dipoles = new DoubleArray(3);
134     DoubleArray quadrupoles = new DoubleArray(9);
135 
136     OpenMMSystem system = openMMEnergy.getSystem();
137     double lambdaElec = system.getLambdaElec();
138 
139     Atom[] atoms = openMMEnergy.getMolecularAssembly().getAtomArray();
140     int nAtoms = atoms.length;
141     for (int i = 0; i < nAtoms; i++) {
142       Atom atom = atoms[i];
143       MultipoleType multipoleType = pme.getMultipoleType(i);
144       PolarizeType polarType = pme.getPolarizeType(i);
145 
146       // Define the frame definition.
147       int axisType = switch (multipoleType.frameDefinition) {
148         case NONE -> OpenMM_AmoebaMultipoleForce_NoAxisType;
149         case ZONLY -> OpenMM_AmoebaMultipoleForce_ZOnly;
150         case ZTHENX -> OpenMM_AmoebaMultipoleForce_ZThenX;
151         case BISECTOR -> OpenMM_AmoebaMultipoleForce_Bisector;
152         case ZTHENBISECTOR -> OpenMM_AmoebaMultipoleForce_ZBisect;
153         case THREEFOLD -> OpenMM_AmoebaMultipoleForce_ThreeFold;
154       };
155 
156       double useFactor = 1.0;
157       if (!atoms[i].getUse() || !atoms[i].getElectrostatics()) {
158         useFactor = 0.0;
159       }
160 
161       double lambdaScale = lambdaElec; // Should be 1.0 at this point.
162       if (!atom.applyLambda()) {
163         lambdaScale = 1.0;
164       }
165 
166       useFactor *= lambdaScale;
167 
168       // Load local multipole coefficients.
169       for (int j = 0; j < 3; j++) {
170         dipoles.set(j, multipoleType.dipole[j] * OpenMM_NmPerAngstrom * useFactor);
171       }
172       int l = 0;
173       for (int j = 0; j < 3; j++) {
174         for (int k = 0; k < 3; k++) {
175           quadrupoles.set(l++, multipoleType.quadrupole[j][k] * quadrupoleConversion * useFactor / 3.0);
176         }
177       }
178 
179       int zaxis = -1;
180       int xaxis = -1;
181       int yaxis = -1;
182       int[] refAtoms = axisAtom[i];
183       if (refAtoms != null) {
184         zaxis = refAtoms[0];
185         if (refAtoms.length > 1) {
186           xaxis = refAtoms[1];
187           if (refAtoms.length > 2) {
188             yaxis = refAtoms[2];
189           }
190         }
191       } else {
192         axisType = OpenMM_AmoebaMultipoleForce_NoAxisType;
193       }
194 
195       double charge = multipoleType.charge * useFactor;
196 
197       // Add the multipole.
198       addMultipole(charge, dipoles, quadrupoles, axisType, zaxis, xaxis, yaxis, polarType.thole,
199           polarType.pdamp * dampingFactorConversion, polarType.polarizability * polarityConversion * polarScale);
200     }
201     dipoles.destroy();
202     quadrupoles.destroy();
203 
204     Crystal crystal = openMMEnergy.getCrystal();
205     if (!crystal.aperiodic()) {
206       setNonbondedMethod(OpenMM_AmoebaMultipoleForce_PME);
207       setCutoffDistance(pme.getEwaldCutoff() * OpenMM_NmPerAngstrom);
208       setAEwald(pme.getEwaldCoefficient() / OpenMM_NmPerAngstrom);
209 
210       double ewaldTolerance = 1.0e-04;
211       setEwaldErrorTolerance(ewaldTolerance);
212 
213       IntArray gridDimensions = new IntArray(3);
214       ReciprocalSpace recip = pme.getReciprocalSpace();
215       gridDimensions.set(0, recip.getXDim());
216       gridDimensions.set(1, recip.getYDim());
217       gridDimensions.set(2, recip.getZDim());
218       setPmeGridDimensions(gridDimensions);
219       gridDimensions.destroy();
220     } else {
221       setNonbondedMethod(OpenMM_AmoebaMultipoleForce_NoCutoff);
222     }
223 
224     setMutualInducedMaxIterations(500);
225     setMutualInducedTargetEpsilon(pme.getPolarEps());
226 
227     int[][] ip11 = pme.getPolarization11();
228 
229     IntArray covalentMap = new IntArray(0);
230     for (int i = 0; i < nAtoms; i++) {
231       Atom ai = atoms[i];
232 
233       // 1-2 Mask
234       covalentMap.resize(0);
235       for (Atom ak : ai.get12List()) {
236         covalentMap.append(ak.getIndex() - 1);
237       }
238       setCovalentMap(i, OpenMM_AmoebaMultipoleForce_Covalent12, covalentMap);
239 
240       // 1-3 Mask
241       covalentMap.resize(0);
242       for (Atom ak : ai.get13List()) {
243         covalentMap.append(ak.getIndex() - 1);
244       }
245       setCovalentMap(i, OpenMM_AmoebaMultipoleForce_Covalent13, covalentMap);
246 
247       // 1-4 Mask
248       covalentMap.resize(0);
249       for (Atom ak : ai.get14List()) {
250         covalentMap.append(ak.getIndex() - 1);
251       }
252       setCovalentMap(i, OpenMM_AmoebaMultipoleForce_Covalent14, covalentMap);
253 
254       // 1-5 Mask
255       covalentMap.resize(0);
256       for (Atom ak : ai.get15List()) {
257         covalentMap.append(ak.getIndex() - 1);
258       }
259       setCovalentMap(i, OpenMM_AmoebaMultipoleForce_Covalent15, covalentMap);
260 
261       // 1-1 Polarization Groups.
262       covalentMap.resize(0);
263       for (int j = 0; j < ip11[i].length; j++) {
264         covalentMap.append(ip11[i][j]);
265       }
266       setCovalentMap(i, OpenMM_AmoebaMultipoleForce_PolarizationCovalent11, covalentMap);
267 
268       // AMOEBA does not scale between 1-2, 1-3, etc. polarization groups.
269     }
270     covalentMap.destroy();
271 
272     int forceGroup = forceField.getInteger("PME_FORCE_GROUP", 1);
273     setForceGroup(forceGroup);
274     logger.log(Level.INFO, format("  AMOEBA polarizable multipole force \t%d", forceGroup));
275 
276     if (scfAlgorithm == SCFAlgorithm.EPT) {
277       logger.info("   Using extrapolated perturbation theory for polarization energy.");
278     }
279   }
280 
281   /**
282    * Convenience method to construct an AMOEBA Multipole Force.
283    *
284    * @param openMMEnergy The OpenMM Energy instance that contains the multipole information.
285    * @return An AMOEBA Multipole Force, or null if there are no multipole interactions.
286    */
287   public static Force constructForce(OpenMMEnergy openMMEnergy) {
288     ParticleMeshEwald pme = openMMEnergy.getPmeNode();
289     if (pme == null) {
290       return null;
291     }
292     return new AmoebaMultipoleForce(openMMEnergy);
293   }
294 
295   public void updateForce(Atom[] atoms, OpenMMEnergy openMMEnergy) {
296     ParticleMeshEwald pme = openMMEnergy.getPmeNode();
297     double quadrupoleConversion = OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom;
298     double polarityConversion = OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom;
299     double dampingFactorConversion = sqrt(OpenMM_NmPerAngstrom);
300 
301     double polarScale = 1.0;
302     if (pme.getPolarizationType() == Polarization.NONE) {
303       polarScale = 0.0;
304     }
305 
306     DoubleArray dipoles = new DoubleArray(3);
307     DoubleArray quadrupoles = new DoubleArray(9);
308 
309     double lambdaElec = openMMEnergy.getSystem().getLambdaElec();
310 
311     for (Atom atom : atoms) {
312       int index = atom.getXyzIndex() - 1;
313       MultipoleType multipoleType = pme.getMultipoleType(index);
314       PolarizeType polarizeType = pme.getPolarizeType(index);
315       int[] axisAtoms = atom.getAxisAtomIndices();
316 
317       double useFactor = 1.0;
318       if (!atom.getUse() || !atom.getElectrostatics()) {
319         useFactor = 0.0;
320       }
321 
322       double lambdaScale = lambdaElec;
323       if (!atom.applyLambda()) {
324         lambdaScale = 1.0;
325       }
326       useFactor *= lambdaScale;
327 
328       // Define the frame definition.
329       int axisType = switch (multipoleType.frameDefinition) {
330         case NONE -> OpenMM_AmoebaMultipoleForce_NoAxisType;
331         case ZONLY -> OpenMM_AmoebaMultipoleForce_ZOnly;
332         case ZTHENX -> OpenMM_AmoebaMultipoleForce_ZThenX;
333         case BISECTOR -> OpenMM_AmoebaMultipoleForce_Bisector;
334         case ZTHENBISECTOR -> OpenMM_AmoebaMultipoleForce_ZBisect;
335         case THREEFOLD -> OpenMM_AmoebaMultipoleForce_ThreeFold;
336       };
337 
338       // Load local multipole coefficients.
339       for (int j = 0; j < 3; j++) {
340         dipoles.set(j, multipoleType.dipole[j] * OpenMM_NmPerAngstrom * useFactor);
341       }
342       int l = 0;
343       for (int j = 0; j < 3; j++) {
344         for (int k = 0; k < 3; k++) {
345           quadrupoles.set(l++, multipoleType.quadrupole[j][k] * quadrupoleConversion / 3.0 * useFactor);
346         }
347       }
348 
349       int zaxis = 1;
350       int xaxis = 1;
351       int yaxis = 1;
352 
353       if (axisAtoms != null) {
354         zaxis = axisAtoms[0];
355         if (axisAtoms.length > 1) {
356           xaxis = axisAtoms[1];
357           if (axisAtoms.length > 2) {
358             yaxis = axisAtoms[2];
359           }
360         }
361       } else {
362         axisType = OpenMM_AmoebaMultipoleForce_NoAxisType;
363       }
364 
365       // Set the multipole parameters.
366       setMultipoleParameters(index, multipoleType.charge * useFactor,
367           dipoles, quadrupoles, axisType, zaxis, xaxis, yaxis,
368           polarizeType.thole, polarizeType.pdamp * dampingFactorConversion,
369           polarizeType.polarizability * polarityConversion * polarScale * useFactor);
370     }
371 
372     dipoles.destroy();
373     quadrupoles.destroy();
374     updateParametersInContext(openMMEnergy.getContext());
375   }
376 
377 }