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.bonded;
39  
40  import static ffx.numerics.math.DoubleMath.dihedralAngle;
41  import static java.lang.System.arraycopy;
42  import static java.util.Arrays.copyOf;
43  import static org.apache.commons.math3.util.FastMath.acos;
44  import static org.apache.commons.math3.util.FastMath.max;
45  import static org.apache.commons.math3.util.FastMath.min;
46  import static org.apache.commons.math3.util.FastMath.sqrt;
47  import static org.apache.commons.math3.util.FastMath.toDegrees;
48  
49  import ffx.numerics.atomic.AtomicDoubleArray3D;
50  import ffx.potential.parameters.BondType;
51  import ffx.potential.parameters.ForceField;
52  import ffx.potential.parameters.StretchTorsionType;
53  import ffx.potential.parameters.TorsionType;
54  
55  import java.io.Serial;
56  import java.util.logging.Logger;
57  
58  /**
59   * The StretchTorsion class represents a coupling between a torsional angle and the three bonds
60   * contained in the torsion, as defined in the 2017 AMOEBA nucleic acid force field.
61   *
62   * @author Michael J. Schnieders
63   * @since 1.0
64   */
65  public class StretchTorsion extends BondedTerm implements LambdaInterface {
66  
67    @Serial
68    private static final long serialVersionUID = 1L;
69  
70    private static final Logger logger = Logger.getLogger(StretchTorsion.class.getName());
71    /** Functional form for OpenMM. */
72    private static final String mathForm;
73  
74    static {
75      /*
76       Defined constants:
77       p1-p4 are particles 1-4.
78       m is a bond number, from 1-3, representing bonds p1-p2, p2-p3, p3-p4.
79       n is a periodicity, from 1-3.
80  
81       k[m][n] is a set of 9 energy constants defined in the parameter file for this stretch-torsion.
82  
83       bVal[m] is the current bond distance for bond m.
84       b[m] is the equilibrium distance for bond m.
85  
86       tVal is the current value of the 1-2-3-4 dihedral angle.
87  
88       phi[m] is a phase offset constant; phi1 = phi3 = 0, phi2 = pi.
89      */
90  
91      StringBuilder mathFormBuilder = new StringBuilder();
92  
93      for (int m = 1; m < 4; m++) {
94        for (int n = 1; n < 4; n++) {
95          // kmn * (bm - bm0) * (1 + cos(n*tors + phi(n)))
96          mathFormBuilder.append(
97              String.format("k%d%d*(bVal%d-b%d)*(1+cos(%d*tVal+phi%d))+", m, n, m, m, n, n));
98        }
99      }
100     int lenStr = mathFormBuilder.length();
101     mathFormBuilder.replace(lenStr - 1, lenStr, ";");
102 
103     for (int m = 1; m < 4; m++) {
104       mathFormBuilder.append(String.format("bVal%d=distance(p%d,p%d);", m, m, (m + 1)));
105     }
106 
107     mathFormBuilder.append("tVal=dihedral(p1,p2,p3,p4)");
108     mathForm = mathFormBuilder.toString();
109   }
110 
111   /**
112    * Stretch Torsion force constants (may be reversed compared to storage in the StretchTorsionType
113    * instance).
114    */
115   private final double[] constants = new double[9];
116   /** First bond force field type. */
117   public BondType bondType1 = null;
118   /** Second bond force field type. */
119   public BondType bondType2 = null;
120   /** Third bond force field type. */
121   public BondType bondType3 = null;
122   /** Value of lambda. */
123   private double lambda = 1.0;
124   /** Value of dE/dL. */
125   private double dEdL = 0.0;
126   /** Flag to indicate lambda dependence. */
127   private boolean lambdaTerm = false;
128   /** Stretch Torsion force field type. */
129   private StretchTorsionType stretchTorsionType = null;
130   /** The StretchTorsion may use more sine and cosine terms than are defined in the TorsionType. */
131   private double[] tsin = new double[] {0.0, 0.0, 0.0};
132 
133   private double[] tcos = new double[] {1.0, 1.0, 1.0};
134 
135   /** Torsion force field type. */
136   private TorsionType torsionType = null;
137 
138   /**
139    * Create a StretchTorsion from 3 connected bonds (no error checking)
140    *
141    * @param b1 Bond
142    * @param b2 Bond
143    * @param b3 Bond
144    */
145   private StretchTorsion(Bond b1, Bond b2, Bond b3) {
146     super();
147     bonds = new Bond[3];
148     bonds[0] = b1;
149     bonds[1] = b2;
150     bonds[2] = b3;
151     initialize();
152   }
153 
154   /**
155    * Torsion Constructor.
156    *
157    * @param n Torsion id
158    */
159   private StretchTorsion(String n) {
160     super(n);
161   }
162 
163   /**
164    * Attempt to create a new StretchTorsion based on the supplied torsion.
165    *
166    * @param torsion the Torsion.
167    * @param forceField the ForceField parameters to apply.
168    * @return a new StretchTorsion, or null.
169    */
170   public static StretchTorsion stretchTorsionFactory(Torsion torsion, ForceField forceField) {
171     TorsionType torsionType = torsion.torsionType;
172     String key = torsionType.getKey();
173     StretchTorsionType stretchTorsionType = forceField.getStretchTorsionType(key);
174     if (stretchTorsionType != null) {
175       Bond bond1 = torsion.bonds[0];
176       Bond middleBond = torsion.bonds[1];
177       Bond bond3 = torsion.bonds[2];
178       StretchTorsion stretchTorsion = new StretchTorsion(bond1, middleBond, bond3);
179       stretchTorsion.stretchTorsionType = stretchTorsionType;
180       stretchTorsion.torsionType = torsion.torsionType;
181       stretchTorsion.bondType1 = bond1.bondType;
182       stretchTorsion.bondType2 = middleBond.bondType;
183       stretchTorsion.bondType3 = bond3.bondType;
184       Atom atom1 = torsion.atoms[0];
185       Atom atom2 = torsion.atoms[1];
186       Atom atom3 = torsion.atoms[2];
187       Atom atom4 = torsion.atoms[3];
188 
189       stretchTorsion.setFlipped(atom1.getAtomType().atomClass != stretchTorsionType.atomClasses[0]
190           || atom2.getAtomType().atomClass != stretchTorsionType.atomClasses[1]
191           || atom3.getAtomType().atomClass != stretchTorsionType.atomClasses[2]
192           || atom4.getAtomType().atomClass != stretchTorsionType.atomClasses[3]);
193 
194       return stretchTorsion;
195     }
196     return null;
197   }
198 
199   /**
200    * Returns the mathematical form of a stretch-torsion as an OpenMM-parsable String.
201    *
202    * @return Mathematical form of the stretch-torsion coupling.
203    */
204   public static String stretchTorsionForm() {
205     return mathForm;
206   }
207 
208   /**
209    * compare
210    *
211    * @param a0 a {@link ffx.potential.bonded.Atom} object.
212    * @param a1 a {@link ffx.potential.bonded.Atom} object.
213    * @param a2 a {@link ffx.potential.bonded.Atom} object.
214    * @param a3 a {@link ffx.potential.bonded.Atom} object.
215    * @return a boolean.
216    */
217   public boolean compare(Atom a0, Atom a1, Atom a2, Atom a3) {
218     if (a0 == atoms[0] && a1 == atoms[1] && a2 == atoms[2] && a3 == atoms[3]) {
219       return true;
220     }
221     return (a0 == atoms[3] && a1 == atoms[2] && a2 == atoms[1] && a3 == atoms[0]);
222   }
223 
224   /**
225    * {@inheritDoc}
226    *
227    * <p>Evaluate the Stretch-Torsion energy.
228    */
229   @Override
230   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
231     energy = 0.0;
232     value = 0.0;
233     dEdL = 0.0;
234     // Only compute this term if at least one atom is being used.
235     if (!getUse()) {
236       return energy;
237     }
238     var atomA = atoms[0];
239     var atomB = atoms[1];
240     var atomC = atoms[2];
241     var atomD = atoms[3];
242     var va = atomA.getXYZ();
243     var vb = atomB.getXYZ();
244     var vc = atomC.getXYZ();
245     var vd = atomD.getXYZ();
246     var vba = vb.sub(va);
247     var vcb = vc.sub(vb);
248     var vdc = vd.sub(vc);
249     var rba2 = vba.length2();
250     var rcb2 = vcb.length2();
251     var rdc2 = vdc.length2();
252     if (min(min(rba2, rcb2), rdc2) == 0.0) {
253       return 0.0;
254     }
255     var rcb = sqrt(rcb2);
256     var t = vba.X(vcb);
257     var u = vcb.X(vdc);
258     var rt2 = max(t.length2(), 0.000001);
259     var ru2 = max(u.length2(), 0.000001);
260     var rtru = sqrt(rt2 * ru2);
261     var vca = vc.sub(va);
262     var vdb = vd.sub(vb);
263     var cosine = t.dot(u) / rtru;
264     var sine = vcb.dot(t.X(u)) / (rcb * rtru);
265     value = toDegrees(acos(cosine));
266     if (sine < 0.0) {
267       value = -value;
268     }
269 
270     // Compute multiple angle trigonometry and phase terms
271     var phi1 = 1.0 + (cosine * tcos[0] + sine * tsin[0]);
272     var dphi1 = (cosine * tsin[0] - sine * tcos[0]);
273     var cosine2 = cosine * cosine - sine * sine;
274     var sine2 = 2.0 * cosine * sine;
275     var phi2 = 1.0 + (cosine2 * tcos[1] + sine2 * tsin[1]);
276     var dphi2 = 2.0 * (cosine2 * tsin[1] - sine2 * tcos[1]);
277     var sine3 = cosine * sine2 + sine * cosine2;
278     var cosine3 = cosine * cosine2 - sine * sine2;
279     var phi3 = 1.0 + (cosine3 * tcos[2] + sine3 * tsin[2]);
280     var dphi3 = 3.0 * (cosine3 * tsin[2] - sine3 * tcos[2]);
281 
282     // Get the stretch-torsion values for the first bond.
283     var c1 = constants[0];
284     var c2 = constants[1];
285     var c3 = constants[2];
286     var rba = sqrt(rba2);
287     var dr1 = rba - bondType1.distance;
288     var units = stretchTorsionType.strTorUnit;
289     var s1 = c1 * phi1 + c2 * phi2 + c3 * phi3;
290     var e1 = units * dr1 * s1;
291 
292     // Get the stretch-torsion values for the second bond.
293     var c4 = constants[3];
294     var c5 = constants[4];
295     var c6 = constants[5];
296     var dr2 = rcb - bondType2.distance;
297     var s2 = c4 * phi1 + c5 * phi2 + c6 * phi3;
298     var e2 = units * dr2 * s2;
299 
300     // Get the stretch-torsion values for the third bond.
301     var c7 = constants[6];
302     var c8 = constants[7];
303     var c9 = constants[8];
304     var rdc = sqrt(rdc2);
305     var dr3 = rdc - bondType3.distance;
306     var s3 = c7 * phi1 + c8 * phi2 + c9 * phi3;
307     var e3 = units * dr3 * s3;
308     energy = e1 + e2 + e3;
309     energy = energy * lambda;
310     dEdL = energy;
311     if (gradient || lambdaTerm) {
312       // Compute derivative components for the first bond.
313       var dedphi = units * dr1 * (c1 * dphi1 + c2 * dphi2 + c3 * dphi3);
314       var ddrd = vba.scale(units * s1 / rba);
315       var dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
316       var dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
317       // Determine chain rule components for the first bond.
318       var ga = dedt.X(vcb).subI(ddrd);
319       var gb = vca.X(dedt).addI(dedu.X(vdc)).addI(ddrd);
320       var gc = dedt.X(vba).addI(vdb.X(dedu));
321       var gd = dedu.X(vcb);
322 
323       // Compute derivative components for the 2nd bond.
324       dedphi = units * dr2 * (c4 * dphi1 + c5 * dphi2 + c6 * dphi3);
325       ddrd = vcb.scale(units * s2 / rcb);
326       dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
327       dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
328       // Accumulate derivative components.
329       ga.addI(dedt.X(vcb));
330       gb.addI(vca.X(dedt).addI(dedu.X(vdc)).subI(ddrd));
331       gc.addI(dedt.X(vba).addI(vdb.X(dedu)).addI(ddrd));
332       gd.addI(dedu.X(vcb));
333 
334       // Compute derivative components for the 3rd bond.
335       dedphi = units * dr3 * (c7 * dphi1 + c8 * dphi2 + c9 * dphi3);
336       ddrd = vdc.scale(units * s3 / rdc);
337       dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
338       dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
339       // Accumulate derivative components.
340       ga.addI(dedt.X(vcb));
341       gb.addI(vca.X(dedt).addI(dedu.X(vdc)));
342       gc.addI(dedt.X(vba).addI(vdb.X(dedu)).subI(ddrd));
343       gd.addI(dedu.X(vcb).addI(ddrd));
344       var ia = atomA.getIndex() - 1;
345       var ib = atomB.getIndex() - 1;
346       var ic = atomC.getIndex() - 1;
347       var id = atomD.getIndex() - 1;
348       if (lambdaTerm) {
349         lambdaGrad.add(threadID, ia, ga);
350         lambdaGrad.add(threadID, ib, gb);
351         lambdaGrad.add(threadID, ic, gc);
352         lambdaGrad.add(threadID, id, gd);
353       }
354       if (gradient) {
355         grad.add(threadID, ia, ga.scaleI(lambda));
356         grad.add(threadID, ib, gb.scaleI(lambda));
357         grad.add(threadID, ic, gc.scaleI(lambda));
358         grad.add(threadID, id, gd.scaleI(lambda));
359       }
360     }
361 
362     return energy;
363   }
364 
365   /**
366    * If the specified atom is not a central atom of <b>this</b> torsion, the atom at the opposite end
367    * is returned. These atoms are said to be 1-4 to each other.
368    *
369    * @param a Atom
370    * @return Atom
371    */
372   public Atom get1_4(Atom a) {
373     if (a == atoms[0]) {
374       return atoms[3];
375     }
376     if (a == atoms[3]) {
377       return atoms[0];
378     }
379     return null;
380   }
381 
382   /**
383    * Returns the array of stretch-torsion constants, in units of kcal/mol/A.
384    *
385    * @return Stretch-torsion constants.
386    */
387   public double[] getConstants() {
388     return copyOf(constants, constants.length);
389   }
390 
391   /** {@inheritDoc} */
392   @Override
393   public double getLambda() {
394     return lambda;
395   }
396 
397   /** {@inheritDoc} */
398   @Override
399   public void setLambda(double lambda) {
400     if (applyAllLambda()) {
401       this.lambda = lambda;
402       lambdaTerm = true;
403     } else {
404       this.lambda = 1.0;
405     }
406   }
407 
408   /** {@inheritDoc} */
409   @Override
410   public double getd2EdL2() {
411     return 0.0;
412   }
413 
414   /** {@inheritDoc} */
415   @Override
416   public double getdEdL() {
417     if (lambdaTerm) {
418       return dEdL;
419     } else {
420       return 0.0;
421     }
422   }
423 
424   /** {@inheritDoc} */
425   @Override
426   public void getdEdXdL(double[] gradient) {
427     // No contribution.
428   }
429 
430   /** Log details for this Stretch-Torsional Angle energy term. */
431   public void log() {
432     logger.info(
433         String.format(
434             " %-8s %6d-%s %6d-%s %6d-%s %6d-%s %10.4f %10.4f",
435             "Stretch-Torsion",
436             atoms[0].getIndex(),
437             atoms[0].getAtomType().name,
438             atoms[1].getIndex(),
439             atoms[1].getAtomType().name,
440             atoms[2].getIndex(),
441             atoms[2].getAtomType().name,
442             atoms[3].getIndex(),
443             atoms[3].getAtomType().name,
444             value,
445             energy));
446   }
447 
448   /**
449    * {@inheritDoc}
450    *
451    * <p>Overridden toString Method returns the Term's id.
452    */
453   @Override
454   public String toString() {
455     return String.format("%s  (%7.1f,%7.2f)", id, value, energy);
456   }
457 
458   /** Initialization */
459   private void initialize() {
460     atoms = new ffx.potential.bonded.Atom[4];
461     atoms[1] = bonds[0].getCommonAtom(bonds[1]);
462     atoms[0] = bonds[0].get1_2(atoms[1]);
463     atoms[2] = bonds[1].get1_2(atoms[1]);
464     atoms[3] = bonds[2].get1_2(atoms[2]);
465     setID_Key(false);
466     value =
467         dihedralAngle(
468             atoms[0].getXYZ(null),
469             atoms[1].getXYZ(null),
470             atoms[2].getXYZ(null),
471             atoms[3].getXYZ(null));
472   }
473 
474   /**
475    * setFlipped.
476    *
477    * @param flipped a boolean.
478    */
479   private void setFlipped(boolean flipped) {
480     if (flipped) {
481       constants[0] = stretchTorsionType.forceConstants[6];
482       constants[1] = stretchTorsionType.forceConstants[7];
483       constants[2] = stretchTorsionType.forceConstants[8];
484       constants[3] = stretchTorsionType.forceConstants[3];
485       constants[4] = stretchTorsionType.forceConstants[4];
486       constants[5] = stretchTorsionType.forceConstants[5];
487       constants[6] = stretchTorsionType.forceConstants[0];
488       constants[7] = stretchTorsionType.forceConstants[1];
489       constants[8] = stretchTorsionType.forceConstants[2];
490     } else {
491       arraycopy(stretchTorsionType.forceConstants, 0, constants, 0, 9);
492     }
493 
494     tsin = new double[] {0.0, 0.0, 0.0};
495     tcos = new double[] {1.0, 1.0, 1.0};
496     arraycopy(torsionType.sine, 0, tsin, 0, min(torsionType.sine.length, 3));
497     arraycopy(torsionType.cosine, 0, tcos, 0, min(torsionType.cosine.length, 3));
498   }
499 }