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.MolecularAssembly;
42  import ffx.potential.parameters.ForceField;
43  import org.apache.commons.configuration2.CompositeConfiguration;
44  
45  import java.util.ArrayList;
46  import java.util.List;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import static ffx.numerics.math.DoubleMath.length;
51  import static java.lang.Double.parseDouble;
52  import static java.lang.Integer.parseInt;
53  import static java.lang.String.format;
54  import static java.lang.System.arraycopy;
55  import static org.apache.commons.math3.util.FastMath.pow;
56  
57  /**
58   * Restrain the position of atoms to their initial coordinates.
59   *
60   * @author Michael J. Schnieders
61   * @since 1.0
62   */
63  public class RestrainPosition extends BondedTerm implements LambdaInterface {
64  
65    private static final Logger logger = Logger.getLogger(RestrainPosition.class.getName());
66  
67    private final double[][] equilibriumCoordinates;
68  
69    /**
70     * Force constant variable stores K/2 in Kcal/mol/A. E = K/2 * dx^2.
71     */
72    private final double forceConstant;
73  
74    /**
75     * Flat bottom radius in Angstroms.
76     */
77    private final double flatBottom;
78  
79    private final double[] a1 = new double[3];
80    private final double[] dx = new double[3];
81    private final double lambdaExp = 1.0;
82    private final double[] lambdaGradient;
83    private final int nAtoms;
84    private final boolean lambdaTerm;
85    private double lambda = 1.0;
86    private double lambdaPow = pow(lambda, lambdaExp);
87    private double dLambdaPow = lambdaExp * pow(lambda, lambdaExp - 1.0);
88    private double d2LambdaPow = 0;
89    private double dEdL = 0.0;
90    private double d2EdL2 = 0.0;
91  
92    /**
93     * Restrain atoms to a position in the global coordinate frame.
94     *
95     * @param atoms                  The atoms to restrain.
96     * @param equilibriumCoordinates The equilibrium coordinates.
97     * @param forceConst             The force constant in kcal/mol/A^2.
98     * @param flatBottom             The flat bottom radius in Angstroms.
99     * @param lambdaTerm             If true, apply lambda to this restraint.
100    */
101   public RestrainPosition(Atom[] atoms, double[][] equilibriumCoordinates,
102                           double forceConst, double flatBottom, boolean lambdaTerm) {
103     this.atoms = atoms;
104     this.equilibriumCoordinates = equilibriumCoordinates;
105     this.forceConstant = forceConst;
106     this.flatBottom = flatBottom;
107     nAtoms = atoms.length;
108     this.lambdaTerm = lambdaTerm;
109     if (lambdaTerm) {
110       lambdaGradient = new double[nAtoms * 3];
111     } else {
112       lambdaGradient = null;
113       lambda = 1.0;
114       lambdaPow = 1.0;
115       dLambdaPow = 0.0;
116       d2LambdaPow = 0.0;
117     }
118 
119     if (logger.isLoggable(Level.FINE)) {
120       logger.info(" RestrainPosition: " + lambdaTerm);
121       for (Atom atom : atoms) {
122         logger.fine(atom.toString());
123       }
124     }
125   }
126 
127   /**
128    * Returns a copy of the atoms array.
129    *
130    * @return Copy of the atom array.
131    */
132   public Atom[] getAtoms() {
133     Atom[] retArray = new Atom[nAtoms];
134     arraycopy(atoms, 0, retArray, 0, nAtoms);
135     return retArray;
136   }
137 
138   /**
139    * Returns the force constant in kcal/mol/Angstrom^2.
140    *
141    * @return a double.
142    */
143   public double getForceConstant() {
144     return forceConstant;
145   }
146 
147   /**
148    * {@inheritDoc}
149    */
150   @Override
151   public double getLambda() {
152     return lambda;
153   }
154 
155   /**
156    * {@inheritDoc}
157    */
158   @Override
159   public void setLambda(double lambda) {
160     if (lambdaTerm) {
161       this.lambda = lambda;
162       double lambdaWindow = 1.0;
163       if (this.lambda <= lambdaWindow) {
164         double dldgl = 1.0 / lambdaWindow;
165         double l = dldgl * this.lambda;
166         double l2 = l * l;
167         double l3 = l2 * l;
168         double l4 = l2 * l2;
169         double l5 = l4 * l;
170         double c3 = 10.0;
171         double c4 = -15.0;
172         double c5 = 6.0;
173         double threeC3 = 3.0 * c3;
174         double sixC3 = 6.0 * c3;
175         double fourC4 = 4.0 * c4;
176         double twelveC4 = 12.0 * c4;
177         double fiveC5 = 5.0 * c5;
178         double twentyC5 = 20.0 * c5;
179         lambdaPow = c3 * l3 + c4 * l4 + c5 * l5;
180         dLambdaPow = (threeC3 * l2 + fourC4 * l3 + fiveC5 * l4) * dldgl;
181         d2LambdaPow = (sixC3 * l + twelveC4 * l2 + twentyC5 * l3) * dldgl * dldgl;
182       } else {
183         lambdaPow = 1.0;
184         dLambdaPow = 0.0;
185         d2LambdaPow = 0.0;
186       }
187     } else {
188       this.lambda = 1.0;
189       lambdaPow = 1.0;
190       dLambdaPow = 0.0;
191       d2LambdaPow = 0.0;
192     }
193   }
194 
195   /**
196    * getNumAtoms.
197    *
198    * @return a int.
199    */
200   public int getNumAtoms() {
201     return nAtoms;
202   }
203 
204   /**
205    * Returns the original coordinates of this restraint, indexed by atoms then x,y,z. This is the
206    * opposite order of the internal storage.
207    *
208    * @return Original coordinates [atoms][xyz]
209    */
210   public double[][] getEquilibriumCoordinates() {
211     double[][] equilibriumCoords = new double[nAtoms][3];
212     for (int i = 0; i < nAtoms; i++) {
213       for (int j = 0; j < 3; j++) {
214         // Mild subtlety here: it is stored internally as [xyz][atoms], but returned as [atoms][xyz]
215         equilibriumCoords[i][j] = equilibriumCoordinates[j][i];
216       }
217     }
218     return equilibriumCoords;
219   }
220 
221   /**
222    * {@inheritDoc}
223    */
224   @Override
225   public double getd2EdL2() {
226     if (lambdaTerm) {
227       return d2EdL2;
228     } else {
229       return 0.0;
230     }
231   }
232 
233   /**
234    * {@inheritDoc}
235    */
236   @Override
237   public double getdEdL() {
238     if (lambdaTerm) {
239       return dEdL;
240     } else {
241       return 0.0;
242     }
243   }
244 
245   /**
246    * {@inheritDoc}
247    */
248   @Override
249   public void getdEdXdL(double[] gradient) {
250     if (lambdaTerm) {
251       int n3 = nAtoms * 3;
252       for (int i = 0; i < n3; i++) {
253         gradient[i] += lambdaGradient[i];
254       }
255     }
256   }
257 
258 
259   /**
260    * Calculates energy and gradients for this coordinate restraint.
261    *
262    * @param gradient Calculate gradients
263    * @return Energy in the coordinate restraint
264    */
265   @Override
266   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
267     double e = 0.0;
268     dEdL = 0.0;
269     d2EdL2 = 0.0;
270     double fx2 = forceConstant * 2.0;
271     for (int i = 0; i < nAtoms; i++) {
272       // Current atomic coordinates.
273       Atom atom = atoms[i];
274       // Compute the separation distance.
275       atom.getXYZ(a1);
276       dx[0] = a1[0] - equilibriumCoordinates[0][i];
277       dx[1] = a1[1] - equilibriumCoordinates[1][i];
278       dx[2] = a1[2] - equilibriumCoordinates[2][i];
279       double r = length(dx);
280       double dr = r;
281       if (flatBottom > 0.0) {
282         dr = Math.max(0.0, r - flatBottom);
283       }
284       value = dr;
285       double dr2 = dr * dr;
286       e += dr2;
287       if (gradient || lambdaTerm) {
288         double scale = fx2 * dr;
289         if (r > 0.0) {
290           scale /= r;
291         }
292         final double dedx = dx[0] * scale;
293         final double dedy = dx[1] * scale;
294         final double dedz = dx[2] * scale;
295         final int index = atom.getIndex() - 1;
296         if (gradient) {
297           grad.add(threadID, index, lambdaPow * dedx, lambdaPow * dedy, lambdaPow * dedz);
298         }
299         if (lambdaTerm) {
300           lambdaGrad.add(threadID, index, dLambdaPow * dedx, dLambdaPow * dedy, dLambdaPow * dedz);
301         }
302       }
303     }
304 
305     if (lambdaTerm) {
306       dEdL = dLambdaPow * forceConstant * e;
307       d2EdL2 = d2LambdaPow * forceConstant * e;
308     }
309 
310     energy = forceConstant * e * lambdaPow;
311 
312     // logger.info(format(" RestrainPosition: %12.6f %12.6f %12.6f", energy, dEdL, d2EdL2));
313 
314     return energy;
315   }
316 
317   public static RestrainPosition[] parseRestrainPositions(MolecularAssembly molecularAssembly) {
318     List<RestrainPosition> restrainPositionList = new ArrayList<>();
319     ForceField forceField = molecularAssembly.getForceField();
320     CompositeConfiguration properties = forceField.getProperties();
321     if (properties.containsKey("restrain-position")) {
322       Atom[] atoms = molecularAssembly.getAtomArray();
323       String[] lines = properties.getStringArray("restrain-position");
324       for (String line : lines) {
325         RestrainPosition restrain = parseRestrainPosition(line, atoms, false);
326         if (restrain != null) {
327           restrainPositionList.add(restrain);
328         }
329       }
330     }
331     if (properties.containsKey("restrain-position-lambda")) {
332       Atom[] atoms = molecularAssembly.getAtomArray();
333       String[] lines = properties.getStringArray("restrain-position-lambda");
334       for (String line : lines) {
335         RestrainPosition restrain = parseRestrainPosition(line, atoms, true);
336         if (restrain != null) {
337           restrainPositionList.add(restrain);
338         }
339       }
340     }
341 
342     if (restrainPositionList.isEmpty()) {
343       return null;
344     }
345 
346     return restrainPositionList.toArray(new RestrainPosition[0]);
347   }
348 
349   /**
350    * Parse a Restrain-Position line and return a RestrainPosition instance.
351    *
352    * @param line      The restraint line.
353    * @param atoms     The atoms in the system.
354    * @param useLambda If true, apply lambda to this restraint.
355    * @return A RestrainPosition object.
356    */
357   public static RestrainPosition parseRestrainPosition(String line, Atom[] atoms, boolean useLambda) {
358     String[] tokens = line.split("\\s+");
359     if (tokens.length < 1) {
360       throw new IllegalArgumentException("Invalid restraint line: " + line);
361     }
362 
363     int nAtoms = atoms.length;
364     Atom[] atomArray;
365     double[][] coordinates;
366     double forceConstant = 50.0;
367     double flatBottom = 0.0;
368     // First token is the atom index.
369     int start = parseInt(tokens[0]);
370     if (start < 0) {
371       // Range of atoms.
372       start = -start - 1;
373       int end = parseInt(tokens[1]) - 1;
374       if (start >= nAtoms || end < start || end >= nAtoms) {
375         logger.severe(format(" Property restrain-position could not be parsed:\n %s.", line));
376         return null;
377       }
378       int n = end - start + 1;
379       atomArray = new Atom[n];
380       coordinates = new double[3][n];
381       for (int j = start, index = 0; j <= end; j++, index++) {
382         atomArray[index] = atoms[j];
383         coordinates[0][index] = atoms[j].getX();
384         coordinates[1][index] = atoms[j].getY();
385         coordinates[2][index] = atoms[j].getZ();
386       }
387       if (tokens.length > 2) {
388         forceConstant = parseDouble(tokens[2]);
389       }
390       if (tokens.length > 3) {
391         flatBottom = parseDouble(tokens[3]);
392       }
393       logger.fine(format(" Restrain-Position of Atoms %d to %d (K=%8.4f, D=%8.4f)",
394           start + 1, end + 1, forceConstant, flatBottom));
395     } else {
396       // Single atom.
397       Atom atom = atoms[start - 1];
398       atomArray = new Atom[1];
399       atomArray[0] = atom;
400       double x = atom.getX();
401       double y = atom.getY();
402       double z = atom.getZ();
403       if (tokens.length > 1) {
404         x = parseDouble(tokens[1]);
405       }
406       if (tokens.length > 2) {
407         y = parseDouble(tokens[2]);
408       }
409       if (tokens.length > 3) {
410         z = parseDouble(tokens[3]);
411       }
412       if (tokens.length > 4) {
413         forceConstant = parseDouble(tokens[4]);
414       }
415       if (tokens.length > 5) {
416         flatBottom = parseDouble(tokens[5]);
417       }
418       logger.fine(format(
419           " Restrain-Position of %s to (%12.6f, %12.6f, %12.6f) with K=%8.4f and D=%8.4f",
420           atom, x, y, z, forceConstant, flatBottom));
421       coordinates = new double[3][1];
422       coordinates[0][0] = x;
423       coordinates[1][0] = y;
424       coordinates[2][0] = z;
425     }
426     return new RestrainPosition(atomArray, coordinates, forceConstant, flatBottom, useLambda);
427   }
428 }