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 org.apache.commons.math3.util.FastMath.acos;
41  import static org.apache.commons.math3.util.FastMath.sqrt;
42  import static org.apache.commons.math3.util.FastMath.toDegrees;
43  
44  import ffx.numerics.atomic.AtomicDoubleArray3D;
45  import ffx.potential.parameters.ForceField;
46  import ffx.potential.parameters.ImproperTorsionType;
47  
48  import java.io.Serial;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.List;
52  import java.util.logging.Logger;
53  
54  /**
55   * The ImproperTorsion class represents an Improper Torsion.
56   *
57   * @author Michael J. Schnieders
58   * @since 1.0
59   */
60  public class ImproperTorsion extends BondedTerm {
61  
62    @Serial
63    private static final long serialVersionUID = 1L;
64  
65    private static final Logger logger = Logger.getLogger(ImproperTorsion.class.getName());
66  
67    /** Force field parameters to compute the ImproperTorsion energy. */
68    public ImproperTorsionType improperType = null;
69    /** Scale factor. */
70    public double scaleFactor = 1.0;
71  
72    /**
73     * ImproperTorsion constructor.
74     *
75     * @param atom1 Atom number 1.
76     * @param atom2 Atom number 2.
77     * @param atom3 Atom number 3.
78     * @param atom4 Atom number 4.
79     */
80    private ImproperTorsion(Atom atom1, Atom atom2, Atom atom3, Atom atom4) {
81      super();
82      atoms = new Atom[4];
83      atoms[0] = atom1;
84      atoms[1] = atom2;
85      atoms[2] = atom3;
86      atoms[3] = atom4;
87      setID_Key(false);
88    }
89  
90    private static void createWildCardImproperTorsion(Atom[] atoms, int[] classes,
91        ImproperTorsionType type, ArrayList<ImproperTorsion> improperTorsions) {
92  
93      // Finalize atom ordering.
94      if (classes[3] == atoms[3].getAtomType().atomClass || type.atomClasses[3] == 0) {
95        // do nothing.
96      } else if (classes[3] == atoms[1].getAtomType().atomClass) {
97        Atom temp = atoms[3];
98        atoms[3] = atoms[1];
99        atoms[1] = temp;
100     } else {
101       Atom temp = atoms[0];
102       atoms[0] = atoms[3];
103       atoms[3] = temp;
104     }
105 
106     if (classes[1] == atoms[1].getAtomType().atomClass || type.atomClasses[1] == 0) {
107       // do nothing.
108     } else if (classes[1] == atoms[0].getAtomType().atomClass) {
109       Atom temp = atoms[1];
110       atoms[1] = atoms[0];
111       atoms[0] = temp;
112     }
113 
114     // One or more zero classes in the improper torsion type
115     ImproperTorsion improperTorsion = new ImproperTorsion(atoms[0], atoms[1], atoms[2], atoms[3]);
116     improperTorsion.setImproperType(type);
117     improperTorsions.add(improperTorsion);
118     improperTorsion.scaleFactor = 1.0 / 3.0;
119     improperTorsions.add(improperTorsion);
120 
121     improperTorsion = new ImproperTorsion(atoms[1], atoms[3], atoms[2], atoms[0]);
122     improperTorsion.setImproperType(type);
123     improperTorsions.add(improperTorsion);
124     improperTorsion.scaleFactor = 1.0 / 3.0;
125     improperTorsions.add(improperTorsion);
126 
127     improperTorsion = new ImproperTorsion(atoms[3], atoms[0], atoms[2], atoms[1]);
128     improperTorsion.setImproperType(type);
129     improperTorsions.add(improperTorsion);
130     improperTorsion.scaleFactor = 1.0 / 3.0;
131     improperTorsions.add(improperTorsion);
132   }
133 
134   /**
135    * ImproperTorsion factory method.
136    *
137    * @param atom Create the improper torsion around this atom.
138    * @param forceField retrieve parameters from this ForceField.
139    * @return the ImproperTorsion if created, or null otherwise.
140    */
141   static ArrayList<ImproperTorsion> improperTorsionFactory(Atom atom, ForceField forceField) {
142 
143     if (atom == null) {
144       return null;
145     }
146 
147     Atom[] atoms = new Atom[4];
148     atoms[2] = atom;
149     List<Bond> bonds = atom.getBonds();
150     if (bonds == null || bonds.size() != 3) {
151       return null;
152     }
153 
154     for (int i = 0; i < 3; i++) {
155       Bond bond = bonds.get(i);
156       Atom atom2 = bond.get1_2(atom);
157       if (i == 2) {
158         atoms[3] = atom2;
159       } else {
160         atoms[i] = atom2;
161       }
162     }
163 
164     ArrayList<ImproperTorsion> improperTorsions = new ArrayList<>();
165     Collection<ImproperTorsionType> types = forceField.getImproperTypes();
166     boolean done = false;
167 
168     // No wild card matches.
169     for (ImproperTorsionType type : types) {
170       int[] classes = new int[4];
171       classes[0] = atoms[0].getAtomType().atomClass;
172       classes[1] = atoms[1].getAtomType().atomClass;
173       classes[2] = atoms[2].getAtomType().atomClass;
174       classes[3] = atoms[3].getAtomType().atomClass;
175       boolean assigned = type.assigned(classes, false, false);
176       if (assigned) {
177         done = true;
178 
179         // Finalize atom ordering.
180         if (classes[3] == atoms[3].getAtomType().atomClass || type.atomClasses[3] == 0) {
181           // do nothing.
182         } else if (classes[3] == atoms[1].getAtomType().atomClass) {
183           Atom temp = atoms[3];
184           atoms[3] = atoms[1];
185           atoms[1] = temp;
186         } else {
187           Atom temp = atoms[0];
188           atoms[0] = atoms[3];
189           atoms[3] = temp;
190         }
191 
192         if (classes[1] == atoms[1].getAtomType().atomClass || type.atomClasses[1] == 0) {
193           // do nothing.
194         } else if (classes[1] == atoms[0].getAtomType().atomClass) {
195           Atom temp = atoms[1];
196           atoms[1] = atoms[0];
197           atoms[0] = temp;
198         }
199 
200         ImproperTorsion improperTorsion =
201             new ImproperTorsion(atoms[0], atoms[1], atoms[2], atoms[3]);
202         improperTorsion.setImproperType(type);
203         improperTorsions.add(improperTorsion);
204         int c0 = type.atomClasses[0];
205         int c1 = type.atomClasses[1];
206         int c3 = type.atomClasses[3];
207         if (c0 == c1 && c1 == c3) {
208           improperTorsion.scaleFactor = 1.0 / 6.0;
209           improperTorsion = new ImproperTorsion(atoms[0], atoms[3], atoms[2], atoms[1]);
210           improperTorsion.setImproperType(type);
211           improperTorsion.scaleFactor = 1.0 / 6.0;
212           improperTorsions.add(improperTorsion);
213           improperTorsion = new ImproperTorsion(atoms[1], atoms[0], atoms[2], atoms[3]);
214           improperTorsion.setImproperType(type);
215           improperTorsion.scaleFactor = 1.0 / 6.0;
216           improperTorsions.add(improperTorsion);
217           improperTorsion = new ImproperTorsion(atoms[1], atoms[3], atoms[2], atoms[0]);
218           improperTorsion.setImproperType(type);
219           improperTorsion.scaleFactor = 1.0 / 6.0;
220           improperTorsions.add(improperTorsion);
221           improperTorsion = new ImproperTorsion(atoms[3], atoms[0], atoms[2], atoms[1]);
222           improperTorsion.setImproperType(type);
223           improperTorsion.scaleFactor = 1.0 / 6.0;
224           improperTorsions.add(improperTorsion);
225           improperTorsion = new ImproperTorsion(atoms[3], atoms[1], atoms[2], atoms[0]);
226           improperTorsion.setImproperType(type);
227           improperTorsion.scaleFactor = 1.0 / 6.0;
228           improperTorsions.add(improperTorsion);
229         } else if (c0 == c1) {
230           improperTorsion.scaleFactor = 0.5;
231           improperTorsion = new ImproperTorsion(atoms[1], atoms[0], atoms[2], atoms[3]);
232           improperTorsion.setImproperType(type);
233           improperTorsion.scaleFactor = 0.5;
234           improperTorsions.add(improperTorsion);
235         } else if (c0 == c3) {
236           improperTorsion.scaleFactor = 0.5;
237           improperTorsion = new ImproperTorsion(atoms[3], atoms[1], atoms[2], atoms[0]);
238           improperTorsion.setImproperType(type);
239           improperTorsion.scaleFactor = 0.5;
240           improperTorsions.add(improperTorsion);
241         } else if (c1 == c3) {
242           improperTorsion.scaleFactor = 0.5;
243           improperTorsion = new ImproperTorsion(atoms[0], atoms[3], atoms[2], atoms[1]);
244           improperTorsion.setImproperType(type);
245           improperTorsion.scaleFactor = 0.5;
246           improperTorsions.add(improperTorsion);
247         }
248       }
249 
250       if (done) {
251         break;
252       }
253     }
254 
255     // Wild card matches for the first or second class.
256     if (!done) {
257       for (ImproperTorsionType type : types) {
258         int[] classes = new int[4];
259         classes[0] = atoms[0].getAtomType().atomClass;
260         classes[1] = atoms[1].getAtomType().atomClass;
261         classes[2] = atoms[2].getAtomType().atomClass;
262         classes[3] = atoms[3].getAtomType().atomClass;
263         boolean assigned = type.assigned(classes, true, false);
264         if (assigned) {
265           done = true;
266           createWildCardImproperTorsion(atoms, classes, type, improperTorsions);
267           break;
268         }
269       }
270     }
271 
272     // Wild card matches for the first, second and third classes.
273     if (!done) {
274       for (ImproperTorsionType type : types) {
275         int[] classes = new int[4];
276         classes[0] = atoms[0].getAtomType().atomClass;
277         classes[1] = atoms[1].getAtomType().atomClass;
278         classes[2] = atoms[2].getAtomType().atomClass;
279         classes[3] = atoms[3].getAtomType().atomClass;
280         boolean assigned = type.assigned(classes, true, true);
281         if (assigned) {
282           createWildCardImproperTorsion(atoms, classes, type, improperTorsions);
283           break;
284         }
285       }
286     }
287 
288     if (improperTorsions.isEmpty()) {
289       return null;
290     }
291 
292     return improperTorsions;
293   }
294 
295   /** {@inheritDoc} */
296   @Override
297   public int compareTo(BondedTerm o) {
298     if (o == null) {
299       throw new NullPointerException();
300     }
301     if (o == this) {
302       return 0;
303     }
304     if (!o.getClass().isInstance(this)) {
305       return super.compareTo(o);
306     }
307     int this1 = atoms[1].getIndex();
308     int a1 = o.atoms[1].getIndex();
309     if (this1 < a1) {
310       return -1;
311     }
312     if (this1 > a1) {
313       return 1;
314     }
315     int this3 = atoms[3].getIndex();
316     int a3 = o.atoms[3].getIndex();
317     return Integer.compare(this3, a3);
318   }
319 
320   /**
321    * {@inheritDoc}
322    *
323    * <p>Evaluate this Improper Torsion energy.
324    */
325   @Override
326   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
327     energy = 0.0;
328     value = 0.0;
329     // Only compute this term if at least one atom is being used.
330     if (!getUse()) {
331       return energy;
332     }
333     var atomA = atoms[0];
334     var atomB = atoms[1];
335     var atomC = atoms[2];
336     var atomD = atoms[3];
337     var va = atomA.getXYZ();
338     var vb = atomB.getXYZ();
339     var vc = atomC.getXYZ();
340     var vd = atomD.getXYZ();
341     var vba = vb.sub(va);
342     var vcb = vc.sub(vb);
343     var vdc = vd.sub(vc);
344     var vt = vba.X(vcb);
345     var vu = vcb.X(vdc);
346     var vtu = vt.X(vu);
347     var rt2 = vt.length2();
348     var ru2 = vu.length2();
349     var rtru = sqrt(rt2 * ru2);
350     if (rtru != 0.0) {
351       // Set the improper torsional parameters for this angle
352       var v2 = improperType.k;
353       var c2 = improperType.cos;
354       var s2 = improperType.sin;
355       // Compute the multiple angle trigonometry and the phase terms
356       var rcb = vcb.length();
357       var cosine = vt.dot(vu) / rtru;
358       var sine = vcb.dot(vtu) / (rcb * rtru);
359       var cosine2 = cosine * cosine - sine * sine;
360       var sine2 = 2.0 * cosine * sine;
361       var phi2 = 1.0 + (cosine2 * c2 + sine2 * s2);
362       var dphi2 = 2.0 * (cosine2 * s2 - sine2 * c2);
363       // Calculate improper torsion energy and master chain rule term
364       value = toDegrees(acos(cosine));
365       var prefactor = improperType.impTorUnit * scaleFactor;
366       energy = prefactor * (v2 * phi2);
367       var dedphi = prefactor * (v2 * dphi2);
368       if (gradient) {
369         // Chain rule terms for first derivative components.
370         var vca = vc.sub(va);
371         var vdb = vd.sub(vb);
372         var dedt = vt.X(vcb).scaleI(dedphi / (rt2 * rcb));
373         var dedu = vu.X(vcb).scaleI(-dedphi / (ru2 * rcb));
374         // Accumulate derivatives.
375         var ia = atomA.getIndex() - 1;
376         var ib = atomB.getIndex() - 1;
377         var ic = atomC.getIndex() - 1;
378         var id = atomD.getIndex() - 1;
379         grad.add(threadID, ia, dedt.X(vcb));
380         grad.add(threadID, ib, dedu.X(vdc).addI(dedt.X(vca).scaleI(-1.0)));
381         grad.add(threadID, ic, dedu.X(vdb).scaleI(-1.0).addI(dedt.X(vba)));
382         grad.add(threadID, id, dedu.X(vcb));
383       }
384     }
385 
386     return energy;
387   }
388 
389   /** Log details for this Improper Torsion energy term. */
390   public void log() {
391     logger.info(
392         String.format(
393             " %s %6d-%s %6d-%s %6d-%s %6d-%s %6.4f %10.4f",
394             "Improper Torsion",
395             atoms[0].getIndex(),
396             atoms[0].getAtomType().name,
397             atoms[1].getIndex(),
398             atoms[1].getAtomType().name,
399             atoms[2].getIndex(),
400             atoms[2].getAtomType().name,
401             atoms[3].getIndex(),
402             atoms[3].getAtomType().name,
403             value,
404             energy));
405   }
406 
407   /**
408    * {@inheritDoc}
409    *
410    * <p>Overridden toString Method returns the Term's id.
411    */
412   @Override
413   public String toString() {
414     return String.format("%s  (%7.1f,%7.2f)", id, value, energy);
415   }
416 
417   /**
418    * Set a reference to the force field parameters for <b>this</b> Angle.
419    *
420    * @param a a {@link ffx.potential.parameters.ImproperTorsionType} object.
421    */
422   private void setImproperType(ImproperTorsionType a) {
423     improperType = a;
424   }
425 }