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.length;
41  import static ffx.numerics.math.DoubleMath.scale;
42  import static ffx.numerics.math.DoubleMath.sub;
43  
44  import ffx.crystal.Crystal;
45  import ffx.numerics.atomic.AtomicDoubleArray3D;
46  import ffx.numerics.switching.ConstantSwitch;
47  import ffx.numerics.switching.UnivariateSwitchingFunction;
48  import ffx.potential.parameters.BondType;
49  
50  import java.io.Serial;
51  import java.util.logging.Logger;
52  
53  /**
54   * RestraintBond class.
55   *
56   * @author Michael J. Schnieders
57   * @since 1.0
58   */
59  public class RestrainDistance extends BondedTerm implements LambdaInterface {
60  
61    @Serial
62    private static final long serialVersionUID = 1L;
63    public static final double DEFAULT_RB_LAM_START = 0.75;
64    public static final double DEFAULT_RB_LAM_END = 1.0;
65    private static final Logger logger = Logger.getLogger(RestrainDistance.class.getName());
66    private final double restraintLambdaStart;
67    private final double restraintLambdaStop;
68    private final double restraintLambdaWindow;
69    private final double rlwInv;
70    private final UnivariateSwitchingFunction switchingFunction;
71    public BondType bondType = null;
72    private final boolean lambdaTerm;
73    private double lambda = 1.0;
74    private double switchVal = 1.0;
75    private double switchdUdL = 1.0;
76    private double switchd2UdL2 = 1.0;
77    private double dEdL = 0.0;
78    private double d2EdL2 = 0.0;
79    private final double[][] dEdXdL = new double[2][3];
80    private final Crystal crystal;
81  
82    /**
83     * Creates a distance restraint between two Atoms.
84     *
85     * @param a1 First Atom.
86     * @param a2 Second Atom.
87     * @param crystal Any Crystal used by the system.
88     * @param lambdaTerm Whether lambda affects this restraint.
89     * @param lamStart At what lambda does the restraint begin to take effect?
90     * @param lamEnd At what lambda does the restraint hit full strength?
91     * @param sf Switching function determining lambda dependence; null produces a ConstantSwitch.
92     */
93    public RestrainDistance(Atom a1, Atom a2, Crystal crystal, boolean lambdaTerm, double lamStart,
94                            double lamEnd, UnivariateSwitchingFunction sf) {
95      restraintLambdaStart = lamStart;
96      restraintLambdaStop = lamEnd;
97      assert lamEnd > lamStart;
98      restraintLambdaWindow = lamEnd - lamStart;
99      rlwInv = 1.0 / restraintLambdaWindow;
100 
101     atoms = new Atom[2];
102 
103     this.crystal = crystal;
104 
105     int i1 = a1.getIndex();
106     int i2 = a2.getIndex();
107     if (i1 < i2) {
108       atoms[0] = a1;
109       atoms[1] = a2;
110     } else {
111       atoms[0] = a2;
112       atoms[1] = a1;
113     }
114     setID_Key(false);
115     switchingFunction = (sf == null ? new ConstantSwitch() : sf);
116     this.lambdaTerm = lambdaTerm;
117     this.setLambda(1.0);
118   }
119 
120   /**
121    * {@inheritDoc}
122    *
123    * <p>Evaluate this Bond energy.
124    */
125   @Override
126   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
127     value = 0.0;
128     energy = 0.0;
129     // Only compute this term if at least one atom is being used.
130     if (!getUse()) {
131       return energy;
132     }
133     Atom atomA = atoms[0];
134     Atom atomB = atoms[1];
135     double[] a0 = new double[3];
136     double[] a1 = new double[3];
137     // The vector from Atom 1 to Atom 0.
138     double[] v10 = new double[3];
139     // Gradient on Atoms 0 & 1.
140     double[] g0 = new double[3];
141     double[] g1 = new double[3];
142     atomA.getXYZ(a0);
143     atomB.getXYZ(a1);
144     sub(a0, a1, v10);
145     if (crystal != null) {
146       crystal.image(v10);
147     }
148     // value is the magnitude of the separation vector
149     value = length(v10);
150     double dv = value - bondType.distance; // bondType.distance = ideal bond length
151     if (bondType.bondFunction.hasFlatBottom()) {
152       if (dv > 0) {
153         dv = Math.max(0, dv - bondType.flatBottomRadius);
154       } else if (dv < 0) {
155         dv = Math.min(0, dv + bondType.flatBottomRadius);
156       } // Else, no adjustments needed.
157     }
158 
159     double dv2 = dv * dv;
160     double kx2 = bondType.bondUnit * bondType.forceConstant * dv2;
161     energy = switchVal * kx2;
162     dEdL = switchdUdL * kx2;
163     d2EdL2 = switchd2UdL2 * kx2;
164     double deddt = 2.0 * bondType.bondUnit * bondType.forceConstant * dv;
165     double de = 0.0;
166 
167     if (value > 0.0) {
168       de = deddt / value;
169     }
170 
171     scale(v10, switchVal * de, g0);
172     scale(v10, -switchVal * de, g1);
173     if (gradient) {
174       grad.add(threadID, atoms[0].getIndex() - 1, g0[0], g0[1], g0[2]);
175       grad.add(threadID, atoms[1].getIndex() - 1, g1[0], g1[1], g1[2]);
176     }
177 
178     // Remove the factor of rL3
179     scale(v10, switchdUdL * de, g0);
180     scale(v10, -switchdUdL * de, g1);
181     dEdXdL[0][0] = g0[0];
182     dEdXdL[0][1] = g0[1];
183     dEdXdL[0][2] = g0[2];
184     dEdXdL[1][0] = g1[0];
185     dEdXdL[1][1] = g1[1];
186     dEdXdL[1][2] = g1[2];
187 
188     value = dv;
189     return energy;
190   }
191 
192   /**
193    * Find the other Atom in <b>this</b> Bond. These two atoms are said to be 1-2.
194    *
195    * @param a The known Atom.
196    * @return The other Atom that makes up <b>this</b> Bond, or Null if Atom <b>a</b> is not part of
197    *     <b>this</b> Bond.
198    */
199   public Atom get1_2(Atom a) {
200     if (a == atoms[0]) {
201       return atoms[1];
202     }
203     if (a == atoms[1]) {
204       return atoms[0];
205     }
206     return null; // Atom not found in bond
207   }
208 
209   /**
210    * Getter for the field <code>bondType</code>.
211    *
212    * @return a {@link ffx.potential.parameters.BondType} object.
213    */
214   public BondType getBondType() {
215     return bondType;
216   }
217 
218   /**
219    * Set a reference to the force field parameters.
220    *
221    * @param bondType a {@link ffx.potential.parameters.BondType} object.
222    */
223   public void setBondType(BondType bondType) {
224     this.bondType = bondType;
225   }
226 
227   /** {@inheritDoc} */
228   @Override
229   public double getLambda() {
230     return lambda;
231   }
232 
233   /** {@inheritDoc} */
234   @Override
235   public void setLambda(double lambda) {
236     this.lambda = lambda;
237     if (lambdaTerm) {
238       if (lambda < restraintLambdaStart) {
239         switchVal = 0.0;
240         switchdUdL = 0;
241         switchd2UdL2 = 0;
242       } else if (lambda > restraintLambdaStop) {
243         switchVal = 1.0;
244         switchdUdL = 0;
245         switchd2UdL2 = 0;
246       } else {
247         double restraintLambda = (lambda - restraintLambdaStart) / restraintLambdaWindow;
248         switchVal = switchingFunction.valueAt(restraintLambda);
249         switchdUdL = rlwInv * switchingFunction.firstDerivative(restraintLambda);
250         switchd2UdL2 = rlwInv * rlwInv * switchingFunction.secondDerivative(restraintLambda);
251       }
252     } else {
253       switchVal = 1.0;
254       switchdUdL = 0.0;
255       switchd2UdL2 = 0.0;
256     }
257   }
258 
259   /** {@inheritDoc} */
260   @Override
261   public double getd2EdL2() {
262     return d2EdL2;
263   }
264 
265   /** {@inheritDoc} */
266   @Override
267   public double getdEdL() {
268 
269     return dEdL;
270   }
271 
272   /** {@inheritDoc} */
273   @Override
274   public void getdEdXdL(double[] gradient) {
275     int i1 = atoms[0].getIndex() - 1;
276     int index = i1 * 3;
277     gradient[index++] += dEdXdL[0][0];
278     gradient[index++] += dEdXdL[0][1];
279     gradient[index] += dEdXdL[0][2];
280     int i2 = atoms[1].getIndex() - 1;
281     index = i2 * 3;
282     gradient[index++] += dEdXdL[1][0];
283     gradient[index++] += dEdXdL[1][1];
284     gradient[index] += dEdXdL[1][2];
285   }
286 
287   @Override
288   public boolean isLambdaScaled() {
289     return lambdaTerm;
290   }
291 
292   /** Log details for this Bond energy term. */
293   public void log() {
294     logger.info(String.format(" %s %6d-%s %6d-%s %6.4f  %6.4f  %10.4f", "Restraint-Bond",
295         atoms[0].getIndex(), atoms[0].getAtomType().name, atoms[1].getIndex(),
296         atoms[1].getAtomType().name, bondType.distance, value, energy));
297     if (!(switchingFunction instanceof ConstantSwitch)) {
298       logger.info(String.format(" Switching function (lambda dependence): %s",
299           switchingFunction.toString()));
300     }
301   }
302 
303   @Override
304   public String toString() {
305     StringBuilder sb = new StringBuilder(String.format(
306         " Distance restraint between atoms %s-%d %s-%d, "
307             + "current distance %10.4g, optimum %10.4g with a %10.4g Angstrom flat bottom, with force constant %10.4g.",
308         atoms[0], atoms[0].getIndex(), atoms[1], atoms[1].getIndex(), value, bondType.distance,
309         bondType.flatBottomRadius, bondType.forceConstant));
310     if (!(switchingFunction instanceof ConstantSwitch)) {
311       sb.append(String.format("\n Switching function (lambda dependence): %s",
312           switchingFunction.toString()));
313     }
314     return sb.toString();
315   }
316 }