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