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