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.algorithms.dynamics.integrators;
39  
40  import ffx.crystal.Crystal;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.bonded.Atom;
43  
44  import java.util.logging.Level;
45  import java.util.logging.Logger;
46  
47  import static java.lang.Math.abs;
48  
49  /**
50   * The Rattle classes implements the RATTLE distance constraint method.
51   *
52   * @author Jill J. Hauer
53   */
54  public class Rattle {
55  
56    /**
57     * Literature reference (from Tinker):
58     *
59     * <p>H. C. Andersen, "RATTLE: A Velocity Version of the SHAKE Algorithm for Molecular Dynamics
60     * Calculations", Journal of Computational Physics, 52, 24-34 (1983)
61     */
62    private static final Logger logger = Logger.getLogger(Rattle.class.getName());
63  
64    private MolecularAssembly molAss;
65    private double[] xold;
66    private double[] yold;
67    private double[] zold;
68    private double[][] vel;
69    private final int nVariables;
70    private final Crystal cryst = molAss.getCrystal();
71  
72    // set the velocity and original coordinates of global variables in Rattle Class
73  
74    /**
75     * Constructor for Rattle.
76     *
77     * @param nVariables a int.
78     * @param molAss a {@link ffx.potential.MolecularAssembly} object.
79     * @param v the initial velocities of the atoms.
80     */
81    public Rattle(int nVariables, MolecularAssembly molAss, double[] v) {
82  
83      int i, j = 0;
84      Atom[] tempArray = molAss.getAtomArray();
85      int size = tempArray.length;
86      this.nVariables = nVariables;
87  
88      // get velocities prior to integrator
89      for (i = 0; i < v.length; i = i + 3) {
90        this.vel[j][0] = v[i];
91        this.vel[j][1] = v[i + 1];
92        this.vel[j][2] = v[i + 2];
93        j++;
94      }
95  
96      // get coordinates prior to integrator
97      for (i = 0; i < size; i++) {
98        this.xold[i] = tempArray[i].getX();
99        this.yold[i] = tempArray[i].getY();
100       this.zold[i] = tempArray[i].getZ();
101     }
102   }
103 
104   // Rattle algorithm applied to distances and half-step velocity values
105 
106   /**
107    * postForce1.
108    *
109    * @param molAss a {@link ffx.potential.MolecularAssembly} object.
110    * @param dt a double.
111    */
112   public void postForce1(MolecularAssembly molAss, double dt) {
113     int i, n;
114     int niter, maxiter;
115     int ia, ib;
116     int nrat; // number of bonds Rattle is applied to (all bonds)
117     double eps, sor;
118     double rateps;
119     double xr, yr, zr;
120     double xo, yo, zo;
121     double dot, rma, rmb;
122     double dist2;
123     double delta;
124     double term;
125     double xterm, yterm, zterm;
126     double[] xyzr = new double[3];
127     double[] xyzo = new double[3];
128     boolean done;
129 
130     n = molAss.getAtomArray().length;
131     nrat = molAss.getBondList().size();
132 
133     // Perform dynamic allocation of some local arrays
134     boolean[] moved = new boolean[n];
135     boolean[] update = new boolean[n];
136     Atom[] atomArray = new Atom[n];
137     double[] x = new double[n];
138     double[] y = new double[n];
139     double[] z = new double[n];
140     double[] krat = new double[nrat]; // equilibrium bond length distances
141     int[][] bondAtmNum = new int[nrat][2]; // rows correspond with bondArray index
142 
143     // initialize a list of all bonds in the system
144     for (i = 0; i < nrat; i++) {
145       bondAtmNum[i][0] = molAss.getBond(i).getAtomArray()[0].getIndex();
146       bondAtmNum[i][1] = molAss.getBond(i).getAtomArray()[1].getIndex();
147       krat[i] = molAss.getBond(i).bondType.distance; // equilibrium distance
148     }
149 
150     // Initialize the lists of atoms previously corrected
151     for (i = 0; i < n; i++) {
152       moved[i] = atomArray[i].isActive();
153 
154       update[i] = false;
155     }
156 
157     // Set the iteration counter, termination and tolerance
158     maxiter = 500;
159     sor = 1.250;
160     rateps = 0.0000010; // default in Tinker
161     eps = rateps;
162 
163     // Apply RATTLE to distances and half-step velocity values
164     niter = 0;
165     done = false;
166 
167     while ((!done) && (niter < maxiter)) {
168       niter++;
169       done = true;
170 
171       for (i = 0; i < nrat; i++) {
172         ia = bondAtmNum[i][0];
173         ib = bondAtmNum[i][1];
174         if (moved[ia] || moved[ib]) {
175           xr = x[ib] - x[ia];
176           yr = y[ib] - y[ia];
177           zr = z[ib] - z[ia];
178           xyzr[0] = xr;
179           xyzr[1] = yr;
180           xyzr[2] = zr;
181 
182           dist2 = cryst.image(xyzr);
183 
184           // update xr, yr, and zr
185           xr = xyzr[0];
186           yr = xyzr[1];
187           zr = xyzr[2];
188           delta = (krat[i] * krat[i]) - dist2;
189 
190           if (abs(delta) > eps) {
191             done = false;
192             update[ia] = true;
193             update[ib] = true;
194 
195             xo = this.xold[ib] - this.xold[ia];
196             yo = this.yold[ib] - this.yold[ia];
197             zo = this.zold[ib] - this.zold[ia];
198             xyzo[0] = xo;
199             xyzo[1] = yo;
200             xyzo[2] = zo;
201 
202             cryst.image(xyzo);
203             xo = xyzo[0];
204             yo = xyzo[1];
205             zo = xyzo[2];
206 
207             dot = (xr * xo) + (yr * yo) + (zr * zo);
208             rma = 1.00 / atomArray[ia].getMass();
209             rmb = 1.00 / atomArray[ib].getMass();
210             term = sor * delta / (2 * (rma + rmb) * dot);
211             xterm = xo * term;
212             yterm = yo * term;
213             zterm = zo * term;
214             // This is where global coordinates should be adjusted
215             x[ia] = x[ia] - (xterm * rma);
216             y[ia] = y[ia] - (yterm * rma);
217             z[ia] = z[ia] - (zterm * rma);
218             x[ib] = x[ib] - (xterm * rmb);
219             y[ib] = y[ib] - (yterm * rmb);
220             z[ib] = z[ib] - (zterm * rmb);
221             rma = rma / dt;
222             rmb = rmb / dt;
223             // This is where global velocity should be adjusted
224             this.vel[ia][0] = this.vel[ia][0] - (xterm * rma);
225             this.vel[ia][1] = this.vel[ia][1] - (yterm * rma);
226             this.vel[ia][2] = this.vel[ia][2] - (zterm * rma);
227             this.vel[ib][0] = this.vel[ib][0] - (xterm * rmb);
228             this.vel[ib][1] = this.vel[ib][1] - (yterm * rmb);
229             this.vel[ib][2] = this.vel[ib][2] - (zterm * rmb);
230           }
231         }
232       }
233 
234       for (i = 0; i < n; i++) {
235         moved[i] = update[i];
236         update[i] = false;
237       }
238     }
239 
240     // Write information on the number of iterations needed
241     if (niter == maxiter) {
242       logger.log(Level.SEVERE, "Rattle: Distance constraints not satisfied.\n");
243     }
244   }
245 
246   /**
247    * postForce2.
248    *
249    * @param dt a double.
250    */
251   public void postForce2(double dt) {
252     int i, n;
253     int ia, ib;
254     int niter, maxiter;
255     int nrat;
256     double eps, sor;
257     double xr, yr, zr;
258     double xv, yv, zv;
259     double dot, rma, rmb;
260     double term;
261     double xterm, yterm, zterm;
262     boolean done;
263     double[] xyzr = new double[3];
264 
265     // Perform dynamic allocation of some local arrays
266     n = molAss.getAtomArray().length;
267     boolean[] moved = new boolean[n];
268     boolean[] update = new boolean[n];
269     boolean[] ratimage =
270         new boolean
271             [molAss.getBondList().size()]; // flag to use minimum image for holonomic constraint
272     Atom[] atomArray = new Atom[n];
273     int[][] bondAtmNum = new int[molAss.getBondList().size()][2];
274     double[] krat = new double[molAss.getBondList().size()];
275     double[] x = new double[molAss.getAtomArray().length];
276     double[] y = new double[molAss.getAtomArray().length];
277     double[] z = new double[molAss.getAtomArray().length];
278 
279     // initialize a list of all atom coordinates in the system
280     n = molAss.getAtomArray().length;
281     for (i = 0; i < n; i++) {
282       x[i] = atomArray[i].getX();
283       y[i] = atomArray[i].getY();
284       z[i] = atomArray[i].getZ();
285     }
286 
287     // initialize a list of all bonds in the system
288     nrat = molAss.getBondList().size();
289     for (i = 0; i < nrat; i++) {
290       bondAtmNum[i][0] = molAss.getBond(i).getAtomArray()[0].getIndex();
291       bondAtmNum[i][1] = molAss.getBond(i).getAtomArray()[1].getIndex();
292       krat[i] = molAss.getBond(i).bondType.distance; // equilibrium distance
293     }
294 
295     // initialize ratimage for minimum image convention
296     for (i = 0; i < nrat; i++) {
297       ratimage[i] = true;
298     }
299 
300     // initialize lists of atoms previously corrected
301     atomArray = molAss.getAtomArray();
302     for (i = 0; i < n; i++) {
303       moved[i] = atomArray[i].isActive();
304 
305       update[i] = false;
306     }
307 
308     // set iteration counter, termination, and tolerance
309     maxiter = 500;
310     niter = 0;
311     done = false;
312     sor = 1.25;
313     eps = 0.000001 / dt;
314 
315     // apply the RATTLE algorithm to correct velocities
316     while ((!done) && (niter < maxiter)) {
317       niter++;
318       done = true;
319       for (i = 1; i < nrat; i++) {
320         ia = bondAtmNum[i][0];
321         ib = bondAtmNum[i][1];
322 
323         if (moved[ia] || moved[ib]) {
324           xr = x[ib] - x[ia];
325           yr = y[ib] - y[ia];
326           zr = z[ib] - z[ia];
327 
328           xyzr[0] = xr;
329           xyzr[1] = yr;
330           xyzr[2] = zr;
331 
332           cryst.image(xyzr);
333           xr = xyzr[0];
334           yr = xyzr[1];
335           zr = xyzr[2];
336 
337           xv = this.vel[ib][0] - this.vel[ia][0];
338           yv = this.vel[ib][1] - this.vel[ia][1];
339           zv = this.vel[ib][2] - this.vel[ia][2];
340           dot = (xr * xv) + (yr * yv) + (zr * zv);
341           rma = 1.00 / atomArray[ia].getMass();
342           rmb = 1.00 / atomArray[ib].getMass();
343           term = -dot / ((rma + rmb) * (krat[i] * krat[i]));
344 
345           if (abs(term) > eps) {
346             done = false;
347             update[ia] = true;
348             update[ib] = true;
349             term = sor * term;
350             xterm = xr * term;
351             yterm = yr * term;
352             zterm = zr * term;
353             this.vel[ia][0] = this.vel[ia][0] - (xterm * rma);
354             this.vel[ia][1] = this.vel[ia][1] - (yterm * rma);
355             this.vel[ia][2] = this.vel[ia][2] - (zterm * rma);
356             this.vel[ib][0] = this.vel[ib][0] + (xterm * rma);
357             this.vel[ib][1] = this.vel[ib][1] + (yterm * rma);
358             this.vel[ib][2] = this.vel[ib][2] + (zterm * rma);
359           }
360         }
361       }
362       for (i = 0; i < n; i++) {
363         moved[i] = update[i];
364         update[i] = false;
365       }
366     }
367 
368     // Write information on the number of iterations needed
369     if (niter == maxiter) {
370       logger.log(Level.SEVERE, "Rattle: Velocity constraints not satisfied.\n");
371     }
372   }
373 }