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