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 ffx.numerics.atomic.AtomicDoubleArray3D;
41  import ffx.potential.parameters.ForceField;
42  import ffx.potential.parameters.PiOrbitalTorsionType;
43  
44  import java.io.Serial;
45  import java.util.logging.Logger;
46  
47  import static java.lang.String.format;
48  import static org.apache.commons.math3.util.FastMath.acos;
49  import static org.apache.commons.math3.util.FastMath.max;
50  import static org.apache.commons.math3.util.FastMath.min;
51  import static org.apache.commons.math3.util.FastMath.sqrt;
52  import static org.apache.commons.math3.util.FastMath.toDegrees;
53  
54  /**
55   * The Pi-Orbital Torsion class.
56   *
57   * @author Michael J. Schnieders
58   * @since 1.0
59   */
60  public class PiOrbitalTorsion extends BondedTerm implements LambdaInterface {
61  
62    @Serial
63    private static final long serialVersionUID = 1L;
64  
65    private static final Logger logger = Logger.getLogger(PiOrbitalTorsion.class.getName());
66  
67    /**
68     * A reference to the Pi-Torsion type in use.
69     */
70    public PiOrbitalTorsionType piOrbitalTorsionType = null;
71    /**
72     * Current value of lambda.
73     */
74    private double lambda = 1.0;
75    /**
76     * Current value of dE/dL.
77     */
78    private double dEdL = 0.0;
79    /**
80     * Flag to indicate use of lambda dependence.
81     */
82    private boolean lambdaTerm = false;
83  
84    /**
85     * Pi-Orbital Torsion constructor.
86     *
87     * @param middleBond a {@link ffx.potential.bonded.Bond} object.
88     */
89    public PiOrbitalTorsion(Bond middleBond) {
90      super();
91      atoms = new Atom[6];
92      atoms[2] = middleBond.atoms[0];
93      atoms[3] = middleBond.atoms[1];
94      bonds = new Bond[5];
95      bonds[2] = middleBond;
96      int i = 0;
97      for (Bond b : atoms[2].getBonds()) {
98        if (b != middleBond) {
99          atoms[i] = b.get1_2(atoms[2]);
100         bonds[i] = b;
101         i++;
102       }
103     }
104     i = 4;
105     for (Bond b : atoms[3].getBonds()) {
106       if (b != middleBond) {
107         atoms[i] = b.get1_2(atoms[3]);
108         bonds[i - 1] = b;
109         i++;
110       }
111     }
112     setID_Key(false);
113   }
114 
115   /**
116    * Get the middle bond that the Pi-Orbital Torsion is formed around.
117    *
118    * @return The middle bond.
119    */
120   public Bond getMiddleBond() {
121     return bonds[2];
122   }
123 
124   /**
125    * Set the PiOrbitalTorsionType.
126    *
127    * @param piOrbitalTorsionType The PiOrbitalTorsionType.
128    */
129   public void setPiOrbitalTorsionType(PiOrbitalTorsionType piOrbitalTorsionType) {
130     this.piOrbitalTorsionType = piOrbitalTorsionType;
131   }
132 
133   /**
134    * Attempt to create a new PiOrbitalTorsion based on the supplied bond and forceField.
135    *
136    * @param bond       the Bond to create a PiOrbitalTorsion around.
137    * @param forceField the ForceField parameters to use.
138    * @return a new PiOrbitalTorsion, or null.
139    */
140   public static PiOrbitalTorsion piOrbitalTorsionFactory(Bond bond, ForceField forceField) {
141     Atom atom1 = bond.getAtom(0);
142     Atom atom2 = bond.getAtom(1);
143     if (!atom1.isTrigonal() || !atom2.isTrigonal()) {
144       return null;
145     }
146 
147     PiOrbitalTorsionType piOrbitalTorsionType = forceField.getPiOrbitalTorsionType(
148         atom1.getAtomType(), atom2.getAtomType());
149     if (piOrbitalTorsionType == null) {
150       return null;
151     }
152 
153     PiOrbitalTorsion piOrbitalTorsion = new PiOrbitalTorsion(bond);
154     piOrbitalTorsion.setPiOrbitalTorsionType(piOrbitalTorsionType);
155     return piOrbitalTorsion;
156   }
157 
158   /**
159    * {@inheritDoc}
160    *
161    * <p>Evaluate the Pi-Orbital Torsion energy.
162    */
163   @Override
164   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
165     energy = 0.0;
166     value = 0.0;
167     dEdL = 0.0;
168     // Only compute this term if at least one atom is being used.
169     if (!getUse()) {
170       return energy;
171     }
172     var atomA = atoms[0];
173     var atomB = atoms[1];
174     var atomC = atoms[2];
175     var atomD = atoms[3];
176     var atomE = atoms[4];
177     var atomF = atoms[5];
178     var va = atomA.getXYZ();
179     var vb = atomB.getXYZ();
180     var vc = atomC.getXYZ();
181     var vd = atomD.getXYZ();
182     var ve = atomE.getXYZ();
183     var vf = atomF.getXYZ();
184     var vad = va.sub(vd);
185     var vbd = vb.sub(vd);
186     var vdc = vd.sub(vc);
187     var vec = ve.sub(vc);
188     var vfc = vf.sub(vc);
189     var vp = vad.X(vbd).addI(vc);
190     var vq = vec.X(vfc).addI(vd);
191     var vpc = vc.sub(vp);
192     var vdq = vq.sub(vd);
193     var vt = vpc.X(vdc);
194     var vu = vdc.X(vdq);
195     var rt2 = vt.length2();
196     var ru2 = vu.length2();
197     var rtru2 = rt2 * ru2;
198     if (rtru2 != 0.0) {
199       var rr = sqrt(rtru2);
200       var rdc = vdc.length();
201       var sine = vdc.dot(vt.X(vu)) / (rdc * rr);
202       var cosine = min(1.0, max(-1.0, vt.dot(vu) / rr));
203       value = toDegrees(acos(cosine));
204       if (sine < 0.0) {
205         value = -value;
206       }
207       if (value > 90.0) {
208         value -= 180.0;
209       }
210       if (value < -90.0) {
211         value += 180.0;
212       }
213       var cosine2 = cosine * cosine - sine * sine;
214       var phi2 = 1.0 - cosine2;
215       energy = piOrbitalTorsionType.piTorsUnit * piOrbitalTorsionType.forceConstant * phi2;
216       dEdL = energy;
217       energy = lambda * energy;
218       if (gradient || lambdaTerm) {
219         var sine2 = 2.0 * cosine * sine;
220         var dphi2 = 2.0 * sine2;
221         var dedphi = piOrbitalTorsionType.piTorsUnit * piOrbitalTorsionType.forceConstant * dphi2;
222 
223         // Chain rule terms for first derivative components.
224         var vdp = vd.sub(vp);
225         var vqc = vq.sub(vc);
226         var vdt = vt.X(vdc).scaleI(dedphi / (rt2 * rdc));
227         var vdu = vu.X(vdc).scaleI(-dedphi / (ru2 * rdc));
228         var dedp = vdt.X(vdc);
229         var dedc = vdp.X(vdt).addI(vdu.X(vdq));
230         var dedd = vdt.X(vpc).addI(vqc.X(vdu));
231         var dedq = vdu.X(vdc);
232 
233         // Atomic gradient.
234         var ga = vbd.X(dedp);
235         var gb = dedp.X(vad);
236         var ge = vfc.X(dedq);
237         var gf = dedq.X(vec);
238         var gc = dedc.add(dedp).subI(ge).subI(gf);
239         var gd = dedd.add(dedq).subI(ga).subI(gb);
240         var iA = atomA.getIndex() - 1;
241         var iB = atomB.getIndex() - 1;
242         var iC = atomC.getIndex() - 1;
243         var iD = atomD.getIndex() - 1;
244         var iE = atomE.getIndex() - 1;
245         var iF = atomF.getIndex() - 1;
246         if (lambdaTerm) {
247           lambdaGrad.add(threadID, iA, ga);
248           lambdaGrad.add(threadID, iB, gb);
249           lambdaGrad.add(threadID, iC, gc);
250           lambdaGrad.add(threadID, iD, gd);
251           lambdaGrad.add(threadID, iE, ge);
252           lambdaGrad.add(threadID, iF, gf);
253         }
254         if (gradient) {
255           grad.add(threadID, iA, ga.scaleI(lambda));
256           grad.add(threadID, iB, gb.scaleI(lambda));
257           grad.add(threadID, iC, gc.scaleI(lambda));
258           grad.add(threadID, iD, gd.scaleI(lambda));
259           grad.add(threadID, iE, ge.scaleI(lambda));
260           grad.add(threadID, iF, gf.scaleI(lambda));
261         }
262       }
263     }
264     return energy;
265   }
266 
267   /**
268    * {@inheritDoc}
269    */
270   @Override
271   public double getLambda() {
272     return lambda;
273   }
274 
275   /**
276    * {@inheritDoc}
277    */
278   @Override
279   public void setLambda(double lambda) {
280     if (applyAllLambda()) {
281       this.lambda = lambda;
282       lambdaTerm = true;
283     } else {
284       this.lambda = 1.0;
285     }
286   }
287 
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public double getd2EdL2() {
293     return 0.0;
294   }
295 
296   /**
297    * {@inheritDoc}
298    */
299   @Override
300   public double getdEdL() {
301     if (lambdaTerm) {
302       return dEdL;
303     } else {
304       return 0.0;
305     }
306   }
307 
308   /**
309    * {@inheritDoc}
310    */
311   @Override
312   public void getdEdXdL(double[] gradient) {
313     // The dEdXdL contributions are zero.
314   }
315 
316   /**
317    * Log details for this Pi-Orbital Torsion energy term.
318    */
319   public void log() {
320     logger.info(format(" %s %6d-%s %6d-%s %10.4f %10.4f", "Pi-Orbital Torsion", atoms[2].getIndex(),
321         atoms[2].getAtomType().name, atoms[3].getIndex(), atoms[3].getAtomType().name, value, energy));
322   }
323 
324   /**
325    * {@inheritDoc}
326    *
327    * <p>Over-ridden toString Method returns the Term's id.
328    */
329   @Override
330   public String toString() {
331     return format("%s  (%7.1f,%7.2f)", id, value, energy);
332   }
333 }