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-2025.
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.TorsionType;
42  
43  import java.io.Serial;
44  import java.util.Arrays;
45  import java.util.function.DoubleUnaryOperator;
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.sqrt;
50  import static org.apache.commons.math3.util.FastMath.toDegrees;
51  
52  /**
53   * RestraintTorsion is a class that restrains the torsion angle defined by four atoms.
54   *
55   * @author Michael J. Schnieders
56   * @see 1.0
57   */
58  public class RestrainTorsion extends BondedTerm implements LambdaInterface {
59  
60    @Serial
61    private static final long serialVersionUID = 1L;
62  
63    private final Atom[] atoms;
64    public final TorsionType torsionType;
65    private final boolean lambdaTerm;
66    private final DoubleUnaryOperator lamMapper;
67    public final double units;
68  
69    private double lambda = 1.0;
70    private double dEdL = 0.0;
71    private double d2EdL2 = 0.0;
72  
73    /**
74     * Constructor for RestrainTorsion.
75     *
76     * @param a1            First torsional atom.
77     * @param a2            Second torsional atom.
78     * @param a3            Third torsional atom.
79     * @param a4            Fourth torsional atom.
80     * @param torsionType   TorsionType.
81     * @param lambdaEnabled True if the lambda term is enabled.
82     * @param reverseLambda True if the lambda term should be reversed.
83     * @param units         Units for the energy term.
84     */
85    public RestrainTorsion(Atom a1, Atom a2, Atom a3, Atom a4, TorsionType torsionType,
86                           boolean lambdaEnabled, boolean reverseLambda, double units) {
87      atoms = new Atom[]{a1, a2, a3, a4};
88      this.torsionType = torsionType;
89      lambdaTerm = lambdaEnabled;
90      if (this.lambdaTerm) {
91        if (reverseLambda) {
92          lamMapper = (double l) -> 1.0 - l;
93        } else {
94          lamMapper = (double l) -> l;
95        }
96      } else {
97        lamMapper = (double l) -> 1.0;
98      }
99      this.units = units;
100   }
101 
102   @Override
103   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
104     energy = 0.0;
105     value = 0.0;
106     dEdL = 0.0;
107     // Only compute this term if at least one atom is being used.
108     if (!getUse()) {
109       return energy;
110     }
111     var atomA = atoms[0];
112     var atomB = atoms[1];
113     var atomC = atoms[2];
114     var atomD = atoms[3];
115     var va = atomA.getXYZ();
116     var vb = atomB.getXYZ();
117     var vc = atomC.getXYZ();
118     var vd = atomD.getXYZ();
119     var vba = vb.sub(va);
120     var vcb = vc.sub(vb);
121     var vdc = vd.sub(vc);
122     var vt = vba.X(vcb);
123     var vu = vcb.X(vdc);
124     var rt2 = vt.length2();
125     var ru2 = vu.length2();
126     var rtru2 = rt2 * ru2;
127     if (rtru2 != 0.0) {
128       var rr = sqrt(rtru2);
129       var rcb = vcb.length();
130       var cosine = vt.dot(vu) / rr;
131       var sine = vcb.dot(vt.X(vu)) / (rcb * rr);
132       value = toDegrees(acos(cosine));
133       if (sine < 0.0) {
134         value = -value;
135       }
136       var amp = torsionType.amplitude;
137       var tsin = torsionType.sine;
138       var tcos = torsionType.cosine;
139       energy = amp[0] * (1.0 + cosine * tcos[0] + sine * tsin[0]);
140       var dedphi = amp[0] * (cosine * tsin[0] - sine * tcos[0]);
141       var cosprev = cosine;
142       var sinprev = sine;
143       var n = torsionType.terms;
144       for (int i = 1; i < n; i++) {
145         var cosn = cosine * cosprev - sine * sinprev;
146         var sinn = sine * cosprev + cosine * sinprev;
147         var phi = 1.0 + cosn * tcos[i] + sinn * tsin[i];
148         var dphi = (1.0 + i) * (cosn * tsin[i] - sinn * tcos[i]);
149         energy = energy + amp[i] * phi;
150         dedphi = dedphi + amp[i] * dphi;
151         cosprev = cosn;
152         sinprev = sinn;
153       }
154       energy = units * energy * lambda;
155       dEdL = units * energy;
156       if (gradient || lambdaTerm) {
157         dedphi = units * dedphi;
158         var vca = vc.sub(va);
159         var vdb = vd.sub(vb);
160         var dedt = vt.X(vcb).scaleI(dedphi / (rt2 * rcb));
161         var dedu = vu.X(vcb).scaleI(-dedphi / (ru2 * rcb));
162         var ga = dedt.X(vcb);
163         var gb = vca.X(dedt).addI(dedu.X(vdc));
164         var gc = dedt.X(vba).addI(vdb.X(dedu));
165         var gd = dedu.X(vcb);
166         int ia = atomA.getIndex() - 1;
167         int ib = atomB.getIndex() - 1;
168         int ic = atomC.getIndex() - 1;
169         int id = atomD.getIndex() - 1;
170         if (lambdaTerm) {
171           lambdaGrad.add(threadID, ia, ga);
172           lambdaGrad.add(threadID, ib, gb);
173           lambdaGrad.add(threadID, ic, gc);
174           lambdaGrad.add(threadID, id, gd);
175         }
176         if (gradient) {
177           grad.add(threadID, ia, ga.scaleI(lambda));
178           grad.add(threadID, ib, gb.scaleI(lambda));
179           grad.add(threadID, ic, gc.scaleI(lambda));
180           grad.add(threadID, id, gd.scaleI(lambda));
181         }
182       }
183     }
184 
185     return energy;
186   }
187 
188   @Override
189   public double getLambda() {
190     return lambda;
191   }
192 
193   public Atom[] getAtoms() {
194     return Arrays.copyOf(atoms, atoms.length);
195   }
196 
197   @Override
198   public Atom getAtom(int index) {
199     return atoms[index];
200   }
201 
202   @Override
203   public boolean applyLambda() {
204     return lambdaTerm;
205   }
206 
207   @Override
208   public void setLambda(double lambda) {
209     this.lambda = lamMapper.applyAsDouble(lambda);
210   }
211 
212   @Override
213   public double getd2EdL2() {
214     return d2EdL2;
215   }
216 
217   @Override
218   public double getdEdL() {
219     return dEdL;
220   }
221 
222   public double mapLambda(double lambda) {
223     return lamMapper.applyAsDouble(lambda);
224   }
225 
226   @Override
227   public void getdEdXdL(double[] gradient) {
228     // The chain rule term is at least supposedly zero.
229   }
230 
231   @Override
232   public String toString() {
233     return format(" Restrain-Torsion %s, Angle: %.3f, Energy: %.3f", torsionType, value, energy);
234   }
235 }