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 static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
41  import static ffx.utilities.Constants.kB;
42  import static java.lang.System.arraycopy;
43  import static java.util.Arrays.copyOf;
44  import static org.apache.commons.math3.util.FastMath.exp;
45  import static org.apache.commons.math3.util.FastMath.sqrt;
46  
47  import ffx.numerics.Constraint;
48  import ffx.potential.SystemState;
49  import ffx.numerics.Potential;
50  import ffx.potential.constraint.ShakeChargeConstraint;
51  
52  import java.util.Random;
53  
54  /**
55   * Stochastic dynamics time step via a velocity Verlet integration algorithm.
56   *
57   * <p>M. P. Allen, "Brownian Dynamics Simulation of a Chemical Reaction in Solution", Molecular
58   * Physics, 40, 1073-1087 (1980)
59   *
60   * <p>F. Guarnieri and W. C. Still, "A Rapidly Convergent Simulation Method: Mixed Monte
61   * Carlo/Stochastic Dynamics", Journal of Computational Chemistry, 15, 1302-1310 (1994)
62   *
63   * @author Michael J. Schnieders
64   * @since 1.0
65   */
66  public class Stochastic extends Integrator {
67  
68    /**
69     * Friction coefficient.
70     */
71    private final double friction;
72    /**
73     * Random number generator.
74     */
75    private final Random random;
76    /**
77     * Per degree of freedom friction.
78     */
79    private final double[] vFriction;
80    /**
81     * Per degree of freedom random velocity change.
82     */
83    private final double[] vRandom;
84    /**
85     * Inverse friction coefficient.
86     */
87    private double inverseFriction;
88    /**
89     * Friction coefficient multiplied by time step.
90     */
91    private double fdt;
92    /**
93     * Exp(-fdt).
94     */
95    private double efdt;
96    /**
97     * Simulation temperature.
98     */
99    private double temperature;
100   private double[] xPrior;
101   private final int nVariables;
102   private double[] africArray;
103 
104   /**
105    * Constructor for Stochastic Dynamics.
106    *
107    * @param friction Friction coefficient.
108    * @param state    The MDState to operate on.
109    */
110   public Stochastic(double friction, SystemState state) {
111     super(state);
112     this.friction = friction;
113     if (friction >= 0) {
114       inverseFriction = 1.0 / friction;
115     } else {
116       inverseFriction = Double.POSITIVE_INFINITY;
117     }
118     nVariables = state.getNumberOfVariables();
119     vFriction = new double[nVariables];
120     vRandom = new double[nVariables];
121     fdt = friction * dt;
122     efdt = exp(-fdt);
123     temperature = 298.15;
124     random = new Random();
125   }
126 
127   /**
128    * {@inheritDoc}
129    *
130    * <p>Use Newton's second law to get the next acceleration and find the full-step velocities using
131    * the Verlet recursion.
132    */
133   @Override
134   public void postForce(double[] gradient) {
135     copyAccelerationToPrevious();
136     double[] a = state.a();
137     double[] v = state.v();
138     double[] mass = state.getMass();
139     for (int i = 0; i < state.getNumberOfVariables(); i++) {
140       double m = mass[i];
141       if (m > 0.0) {
142         a[i] = -KCAL_TO_GRAM_ANG2_PER_PS2 * gradient[i] / m;
143         v[i] += (0.5 * a[i] * vFriction[i] + vRandom[i]);
144       }
145     }
146   }
147 
148   /**
149    * {@inheritDoc}
150    *
151    * <p>Set the frictional and random coefficients, store the current atom positions, then find new
152    * atom positions and half-step velocities via Verlet recursion.
153    */
154   @Override
155   public void preForce(Potential potential) throws RuntimeException {
156     double[] mass = state.getMass();
157     double[] x = state.x();
158     double[] v = state.v();
159     double[] a = state.a();
160     if (useConstraints) {
161       if (xPrior == null) {
162         xPrior = copyOf(x, nVariables);
163       } else {
164         arraycopy(x, 0, xPrior, 0, nVariables);
165       }
166       if(africArray == null){
167         africArray = new double[nVariables];
168       }
169     }
170     for (int i = 0; i < state.getNumberOfVariables(); i++) {
171       double m = mass[i];
172       double afric;
173       double pfric;
174       double prand;
175       if (fdt <= 0.0) {
176         // In the limit of no friction, SD recovers normal molecular dynamics.
177         pfric = 1.0;
178         vFriction[i] = dt;
179         afric = 0.5 * dt * dt;
180         prand = 0.0;
181         vRandom[i] = 0.0;
182       } else {
183         double pterm;
184         double vterm;
185         double rho;
186         if (fdt >= 0.05) {
187           // Analytical expressions when the friction coefficient is large.
188           pfric = efdt;
189           vFriction[i] = (1.0 - efdt) * inverseFriction;
190           afric = (dt - vFriction[i]) * inverseFriction;
191           pterm = 2.0 * fdt - 3.0 + (4.0 - efdt) * efdt;
192           vterm = 1.0 - efdt * efdt;
193           rho = (1.0 - efdt) * (1.0 - efdt) / sqrt(pterm * vterm);
194         } else {
195           // Use a series expansions when friction coefficient is small.
196           double fdt2 = fdt * fdt;
197           double fdt3 = fdt * fdt2;
198           double fdt4 = fdt * fdt3;
199           double fdt5 = fdt * fdt4;
200           double fdt6 = fdt * fdt5;
201           double fdt7 = fdt * fdt6;
202           double fdt8 = fdt * fdt7;
203           double fdt9 = fdt * fdt8;
204           afric =
205                   (fdt2 / 2.0 - fdt3 / 6.0 + fdt4 / 24.0 - fdt5 / 120.0 + fdt6 / 720.0 - fdt7 / 5040.0
206                           + fdt8 / 40320.0 - fdt9 / 362880.0) / (friction * friction);
207           vFriction[i] = dt - friction * afric;
208           pfric = 1.0 - friction * vFriction[i];
209           pterm =
210                   2.0 * fdt3 / 3.0 - fdt4 / 2.0 + 7.0 * fdt5 / 30.0 - fdt6 / 12.0 + 31.0 * fdt7 / 1260.0
211                           - fdt8 / 160.0 + 127.0 * fdt9 / 90720.0;
212           vterm = 2.0 * fdt - 2.0 * fdt2 + 4.0 * fdt3 / 3.0 - 2.0 * fdt4 / 3.0 + 4.0 * fdt5 / 15.0
213                   - 4.0 * fdt6 / 45.0 + 8.0 * fdt7 / 315.0 - 2.0 * fdt8 / 315.0 + 4.0 * fdt9 / 2835.0;
214           rho = sqrt(3.0) * (0.5 - fdt / 16.0 - 17.0 * fdt2 / 1280.0 + 17.0 * fdt3 / 6144.0
215                   + 40967.0 * fdt4 / 34406400.0 - 57203.0 * fdt5 / 275251200.0
216                   - 1429487.0 * fdt6 / 13212057600.0 + 1877509.0 * fdt7 / 105696460800.0);
217         }
218         // Compute random terms to thermostat the nonzero friction case.
219         double ktm = kB * temperature / m;
220         double psig = sqrt(ktm * pterm) / friction;
221         double vsig = sqrt(ktm * vterm);
222         double rhoc = sqrt(1.0 - rho * rho);
223         double pnorm = random.nextGaussian();
224         double vnorm = random.nextGaussian();
225         prand = psig * pnorm;
226         vRandom[i] = vsig * (rho * pnorm + rhoc * vnorm);
227       }
228 
229       // Store the current atom positions,
230       // then find new atom positions and half-step velocities via Verlet recursion.
231       x[i] += (v[i] * vFriction[i] + a[i] * afric + prand);
232       v[i] = v[i] * pfric + 0.5 * a[i] * vFriction[i];
233 
234       if(useConstraints){
235         africArray[i] = afric;
236       }
237     }
238     if (useConstraints) {
239       for(Constraint c : constraints){
240         if(c instanceof ShakeChargeConstraint){
241           ((ShakeChargeConstraint) c).applyChargeConstraintToStep(x, africArray, mass, dt);
242           double velScale = 1.0 / dt;
243           for (int i = 0; i < nVariables; i++) {
244             v[i] = velScale * (x[i] - xPrior[i]);
245           }
246         } else {
247           c.applyConstraintToStep(xPrior, x, mass, constraintTolerance);
248         }
249       }
250     }
251   }
252 
253   /**
254    * Initialize the Random number generator used to apply random forces to the particles.
255    *
256    * @param seed Random number generator seed.
257    */
258   public void setRandomSeed(long seed) {
259     random.setSeed(seed);
260   }
261 
262   /**
263    * Setter for the field <code>temperature</code>.
264    *
265    * @param temperature a double.
266    */
267   public void setTemperature(double temperature) {
268     this.temperature = temperature;
269   }
270 
271   /**
272    * {@inheritDoc}
273    *
274    * <p>Set the stochastic dynamics time-step.
275    */
276   @Override
277   public void setTimeStep(double dt) {
278     this.dt = dt;
279     fdt = friction * dt;
280     efdt = exp(-fdt);
281     if (friction >= 0) {
282       inverseFriction = 1.0 / friction;
283     } else {
284       inverseFriction = Double.POSITIVE_INFINITY;
285     }
286   }
287 
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public String toString() {
293     return "Stochastic";
294   }
295 }