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 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.potential.SystemState;
48  import ffx.numerics.Potential;
49  import ffx.potential.constraint.ShakeChargeConstraint;
50  
51  import java.util.Arrays;
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 
101   /**
102    * Constructor for Stochastic Dynamics.
103    *
104    * @param friction Friction coefficient.
105    * @param state    The MDState to operate on.
106    */
107   public Stochastic(double friction, SystemState state) {
108     super(state);
109     this.friction = friction;
110     if (friction >= 0) {
111       inverseFriction = 1.0 / friction;
112     } else {
113       inverseFriction = Double.POSITIVE_INFINITY;
114     }
115     int nVariables = state.getNumberOfVariables();
116     vFriction = new double[nVariables];
117     vRandom = new double[nVariables];
118     fdt = friction * dt;
119     efdt = exp(-fdt);
120     temperature = 298.15;
121     random = new Random();
122   }
123 
124   /**
125    * {@inheritDoc}
126    *
127    * <p>Use Newton's second law to get the next acceleration and find the full-step velocities using
128    * the Verlet recursion.
129    */
130   @Override
131   public void postForce(double[] gradient) {
132     copyAccelerationToPrevious();
133     double[] a = state.a();
134     double[] v = state.v();
135     double[] mass = state.getMass();
136     for (int i = 0; i < state.getNumberOfVariables(); i++) {
137       double m = mass[i];
138       if (m > 0.0) {
139         a[i] = -KCAL_TO_GRAM_ANG2_PER_PS2 * gradient[i] / m;
140         v[i] += (0.5 * a[i] * vFriction[i] + vRandom[i]);
141       }
142     }
143   }
144 
145   /**
146    * {@inheritDoc}
147    *
148    * <p>Set the frictional and random coefficients, store the current atom positions, then find new
149    * atom positions and half-step velocities via Verlet recursion.
150    */
151   @Override
152   public void preForce(Potential potential) throws RuntimeException {
153     boolean useChargeConstraint = false;
154     if (!constraints.isEmpty()) {
155       useChargeConstraint = constraints.get(0) instanceof ShakeChargeConstraint;
156     }
157     ShakeChargeConstraint chargeConstraint = null;
158     boolean done = false;
159     int maxIter = 5000;
160     int iter = 0;
161     double[] mass = state.getMass();
162     double[] x = state.x();
163     double[] v = state.v();
164     double[] a = state.a();
165     double[] aConstrained = new double[a.length];
166     Arrays.fill(aConstrained, 0.0);
167     if (useChargeConstraint) {
168       chargeConstraint = (ShakeChargeConstraint) constraints.get(0);
169     }
170     while (!done && iter < maxIter) {
171       iter++;
172       for (int i = 0; i < state.getNumberOfVariables(); i++) {
173         double m = mass[i];
174         if (m <= 0.0) {
175           continue;
176         }
177         double pfric;
178         double afric;
179         double prand;
180         if (fdt <= 0.0) {
181           // In the limit of no friction, SD recovers normal molecular dynamics.
182           pfric = 1.0;
183           vFriction[i] = dt;
184           afric = 0.5 * dt * dt;
185           prand = 0.0;
186           vRandom[i] = 0.0;
187         } else {
188           double pterm;
189           double vterm;
190           double rho;
191           if (fdt >= 0.05) {
192             // Analytical expressions when the friction coefficient is large.
193             pfric = efdt;
194             vFriction[i] = (1.0 - efdt) * inverseFriction;
195             afric = (dt - vFriction[i]) * inverseFriction;
196             pterm = 2.0 * fdt - 3.0 + (4.0 - efdt) * efdt;
197             vterm = 1.0 - efdt * efdt;
198             rho = (1.0 - efdt) * (1.0 - efdt) / sqrt(pterm * vterm);
199           } else {
200             // Use a series expansions when friction coefficient is small.
201             double fdt2 = fdt * fdt;
202             double fdt3 = fdt * fdt2;
203             double fdt4 = fdt * fdt3;
204             double fdt5 = fdt * fdt4;
205             double fdt6 = fdt * fdt5;
206             double fdt7 = fdt * fdt6;
207             double fdt8 = fdt * fdt7;
208             double fdt9 = fdt * fdt8;
209             afric =
210                 (fdt2 / 2.0 - fdt3 / 6.0 + fdt4 / 24.0 - fdt5 / 120.0 + fdt6 / 720.0 - fdt7 / 5040.0
211                     + fdt8 / 40320.0 - fdt9 / 362880.0) / (friction * friction);
212             vFriction[i] = dt - friction * afric;
213             pfric = 1.0 - friction * vFriction[i];
214             pterm =
215                 2.0 * fdt3 / 3.0 - fdt4 / 2.0 + 7.0 * fdt5 / 30.0 - fdt6 / 12.0 + 31.0 * fdt7 / 1260.0
216                     - fdt8 / 160.0 + 127.0 * fdt9 / 90720.0;
217             vterm = 2.0 * fdt - 2.0 * fdt2 + 4.0 * fdt3 / 3.0 - 2.0 * fdt4 / 3.0 + 4.0 * fdt5 / 15.0
218                 - 4.0 * fdt6 / 45.0 + 8.0 * fdt7 / 315.0 - 2.0 * fdt8 / 315.0 + 4.0 * fdt9 / 2835.0;
219             rho = sqrt(3.0) * (0.5 - fdt / 16.0 - 17.0 * fdt2 / 1280.0 + 17.0 * fdt3 / 6144.0
220                 + 40967.0 * fdt4 / 34406400.0 - 57203.0 * fdt5 / 275251200.0
221                 - 1429487.0 * fdt6 / 13212057600.0 + 1877509.0 * fdt7 / 105696460800.0);
222           }
223           // Compute random terms to thermostat the nonzero friction case.
224           double ktm = kB * temperature / m;
225           double psig = sqrt(ktm * pterm) / friction;
226           double vsig = sqrt(ktm * vterm);
227           double rhoc = sqrt(1.0 - rho * rho);
228           double pnorm = random.nextGaussian();
229           double vnorm = random.nextGaussian();
230           prand = psig * pnorm;
231           vRandom[i] = vsig * (rho * pnorm + rhoc * vnorm);
232         }
233 
234         // Store the current atom positions,
235         // then find new atom positions and half-step velocities via Verlet recursion.
236 
237         if (iter == 1) {
238           x[i] += (v[i] * vFriction[i] + a[i] * afric + prand);
239           v[i] = v[i] * pfric + 0.5 * a[i] * vFriction[i];
240         } else {
241           x[i] += (aConstrained[i] * afric);
242         }
243       }
244       done = true;
245       if (useChargeConstraint) {
246         done = chargeConstraint.applyChargeConstraintToStep(x, aConstrained, mass, dt);
247       }
248     }
249     if (iter == maxIter) {
250       throw new RuntimeException("SHAKE  --  Warning, Distance Constraints not Satisfied");
251     }
252   }
253 
254   /**
255    * Initialize the Random number generator used to apply random forces to the particles.
256    *
257    * @param seed Random number generator seed.
258    */
259   public void setRandomSeed(long seed) {
260     random.setSeed(seed);
261   }
262 
263   /**
264    * Setter for the field <code>temperature</code>.
265    *
266    * @param temperature a double.
267    */
268   public void setTemperature(double temperature) {
269     this.temperature = temperature;
270   }
271 
272   /**
273    * {@inheritDoc}
274    *
275    * <p>Set the stochastic dynamics time-step.
276    */
277   @Override
278   public void setTimeStep(double dt) {
279     this.dt = dt;
280     fdt = friction * dt;
281     efdt = exp(-fdt);
282     if (friction >= 0) {
283       inverseFriction = 1.0 / friction;
284     } else {
285       inverseFriction = Double.POSITIVE_INFINITY;
286     }
287   }
288 
289   /**
290    * {@inheritDoc}
291    */
292   @Override
293   public String toString() {
294     return "Stochastic";
295   }
296 }