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.thermostats;
39  
40  import ffx.numerics.Constraint;
41  import ffx.numerics.Potential.VARIABLE_TYPE;
42  import ffx.potential.SystemState;
43  
44  import java.util.ArrayList;
45  import java.util.List;
46  import java.util.Random;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
51  import static ffx.utilities.Constants.kB;
52  import static java.lang.String.format;
53  import static org.apache.commons.math3.util.FastMath.max;
54  import static org.apache.commons.math3.util.FastMath.sqrt;
55  
56  /**
57   * The abstract Thermostat class implements methods common to all thermostats for initializing
58   * velocities from a Maxwell-Boltzmann distribution and computing the instantaneous temperature.
59   * Abstract methods are declared for half-step and full-step modification of velocities for
60   * thermostat implementations.
61   *
62   * @author Michael J. Schnieders
63   * @since 1.0
64   */
65  public abstract class Thermostat {
66  
67    private static final Logger logger = Logger.getLogger(Thermostat.class.getName());
68    /**
69     * The center of mass coordinates.
70     */
71    private final double[] centerOfMass = new double[3];
72    /**
73     * The linear momentum.
74     */
75    private final double[] linearMomentum = new double[3];
76    /**
77     * The angular momentum.
78     */
79    private final double[] angularMomentum = new double[3];
80    /**
81     * Number of degrees of freedom removed by constraints.
82     */
83    private final int constrainedDoF;
84    /**
85     * The identity of this Thermostat.
86     */
87    protected ThermostatEnum name;
88    /**
89     * The value of kT in kcal/mol at the target temperature.
90     */
91    protected double kT;
92    /**
93     * Number of degrees of freedom, which can be less than the number of variables. For example,
94     * removing translational motion removes 3 degrees of freedom.
95     */
96    protected int degreesOfFreedom;
97    /**
98     * The molecular dynamics state to be used.
99     */
100   protected final SystemState state;
101   /**
102    * The type of each variable.
103    */
104   protected VARIABLE_TYPE[] type;
105   /**
106    * The random number generator that the Thermostat will use to initialize velocities.
107    */
108   protected Random random;
109   /**
110    * Any geometric constraints to apply during integration.
111    */
112   protected List<Constraint> constraints;
113   /**
114    * The target temperature that this thermostat should maintain.
115    */
116   double targetTemperature;
117   /**
118    * Flag to indicate that center of mass motion should be removed.
119    */
120   private boolean removeCenterOfMassMotion;
121   /**
122    * Reduce logging.
123    */
124   private boolean quiet = false;
125 
126   /**
127    * Constructor for Thermostat.
128    *
129    * @param state             The molecular dynamics state to be used.
130    * @param type              the VARIABLE_TYPE of each variable.
131    * @param targetTemperature a double.
132    */
133   public Thermostat(SystemState state, VARIABLE_TYPE[] type, double targetTemperature) {
134     this(state, type, targetTemperature, new ArrayList<>());
135   }
136 
137   public Thermostat(SystemState state, VARIABLE_TYPE[] type, double targetTemperature, List<Constraint> constraints) {
138     this.state = state;
139     this.type = type;
140     int n = state.getNumberOfVariables();
141     assert (n > 3);
142     assert (type.length == n);
143     random = new Random();
144     setTargetTemperature(targetTemperature);
145 
146     this.constraints = new ArrayList<>(constraints);
147     // Not every type of constraint constrains just one DoF.
148     // SETTLE constraints, for example, constrain three.
149     double nConstrained = constraints.stream().mapToInt(Constraint::getNumDegreesFrozen).sum();
150 
151     double[] mass = state.getMass();
152     int massConstrained = 0;
153     for (int i = 0; i < n; i++) {
154       if (mass[i] <= 0.0) {
155         massConstrained++;
156       }
157     }
158 
159     if (massConstrained > 0 && nConstrained > 0) {
160       logger.severe("Mass-constraints with other constraints are not supported.");
161     }
162     constrainedDoF = (int) max(nConstrained, massConstrained);
163 
164     removeCenterOfMassMotion = true;
165 
166     // Remove center of mass motion degrees of freedom.
167     degreesOfFreedom = n - 3 - constrainedDoF;
168 
169     // Update the kinetic energy.
170     computeKineticEnergy();
171   }
172 
173   /**
174    * Parse a string into a Thermostat enumeration.
175    *
176    * @param str Thermostat String.
177    * @return An instance of the ThermostatEnum.
178    */
179   public static ThermostatEnum parseThermostat(String str) {
180     try {
181       return ThermostatEnum.valueOf(str.toUpperCase());
182     } catch (Exception e) {
183       logger.info(format(" Could not parse %s as a thermostat; defaulting to Berendsen.", str));
184       return ThermostatEnum.BERENDSEN;
185     }
186   }
187 
188   /**
189    * Compute the center of mass, linear momentum and angular momentum.
190    *
191    * @param remove If true, the center of mass motion will be removed.
192    * @param print  If true, the center of mass and momenta will be printed.
193    */
194   public void centerOfMassMotion(boolean remove, boolean print) {
195     double totalMass = 0.0;
196     for (int i = 0; i < 3; i++) {
197       centerOfMass[i] = 0.0;
198       linearMomentum[i] = 0.0;
199       angularMomentum[i] = 0.0;
200     }
201 
202     int nVariables = state.getNumberOfVariables();
203     double[] x = state.x();
204     double[] v = state.v();
205     double[] mass = state.getMass();
206 
207     int index = 0;
208     while (index < nVariables) {
209       double m = mass[index];
210       if (m <= 0.0 || type[index] == VARIABLE_TYPE.OTHER) {
211         index++;
212         continue;
213       }
214       assert (type[index] == VARIABLE_TYPE.X);
215       double xx = x[index];
216       double vx = v[index++];
217       assert (type[index] == VARIABLE_TYPE.Y);
218       double yy = x[index];
219       double vy = v[index++];
220       assert (type[index] == VARIABLE_TYPE.Z);
221       double zz = x[index];
222       double vz = v[index++];
223       totalMass += m;
224       centerOfMass[0] += xx * m;
225       centerOfMass[1] += yy * m;
226       centerOfMass[2] += zz * m;
227       linearMomentum[0] += vx * m;
228       linearMomentum[1] += vy * m;
229       linearMomentum[2] += vz * m;
230       angularMomentum[0] += (yy * vz - zz * vy) * m;
231       angularMomentum[1] += (zz * vx - xx * vz) * m;
232       angularMomentum[2] += (xx * vy - yy * vx) * m;
233     }
234 
235     angularMomentum[0] -= (centerOfMass[1] * linearMomentum[2] - centerOfMass[2] * linearMomentum[1]) / totalMass;
236     angularMomentum[1] -= (centerOfMass[2] * linearMomentum[0] - centerOfMass[0] * linearMomentum[2]) / totalMass;
237     angularMomentum[2] -= (centerOfMass[0] * linearMomentum[1] - centerOfMass[1] * linearMomentum[0]) / totalMass;
238     centerOfMass[0] /= totalMass;
239     centerOfMass[1] /= totalMass;
240     centerOfMass[2] /= totalMass;
241     linearMomentum[0] /= totalMass;
242     linearMomentum[1] /= totalMass;
243     linearMomentum[2] /= totalMass;
244 
245     if (print) {
246       String sb = format(
247           "  Center of Mass   (%12.3f,%12.3f,%12.3f)\n  Linear Momentum  (%12.3f,%12.3f,%12.3f)\n  Angular Momentum (%12.3f,%12.3f,%12.3f)",
248           centerOfMass[0], centerOfMass[1], centerOfMass[2], linearMomentum[0], linearMomentum[1],
249           linearMomentum[2], angularMomentum[0], angularMomentum[1], angularMomentum[2]);
250       logger.info(sb);
251     }
252 
253     if (remove) {
254       removeCenterOfMassMotion(print);
255       centerOfMassMotion(false, print);
256     }
257   }
258 
259   /**
260    * Compute the current temperature and kinetic energy of the system.
261    */
262   public final void computeKineticEnergy() {
263     double e = 0.0;
264     double[] v = state.v();
265     double[] mass = state.getMass();
266     for (int i = 0; i < state.getNumberOfVariables(); i++) {
267       double m = mass[i];
268       if (m > 0.0) {
269         double velocity = v[i];
270         double v2 = velocity * velocity;
271         e += m * v2;
272       }
273     }
274     state.setTemperature(e / (kB * degreesOfFreedom));
275     e *= 0.5 / KCAL_TO_GRAM_ANG2_PER_PS2;
276     state.setKineticEnergy(e);
277   }
278 
279   /**
280    * The half-step temperature correction.
281    *
282    * @param dt a double.
283    */
284   public abstract void halfStep(double dt);
285 
286   /**
287    * The full-step temperature correction.
288    *
289    * @param dt a double.
290    */
291   public abstract void fullStep(double dt);
292 
293   /**
294    * Get the current temperature.
295    *
296    * <p>This depends on a previous call to the computeKineticEnergy.
297    *
298    * @return Current temperature.
299    */
300   public double getCurrentTemperature() {
301     return state.getTemperature();
302   }
303 
304   /**
305    * Return the number of degrees of freedom.
306    *
307    * @return Degrees of freedom.
308    */
309   public int getDegreesOfFreedom() {
310     return degreesOfFreedom;
311   }
312 
313   /**
314    * Get the current kinetic energy.
315    *
316    * <p>This depends on a previous call to the computeKineticEnergy.
317    *
318    * @return Kinetic energy.
319    */
320   public double getKineticEnergy() {
321     return state.getKineticEnergy();
322   }
323 
324   /**
325    * Getter for the field <code>removeCenterOfMassMotion</code>.
326    *
327    * @return a boolean.
328    */
329   public boolean getRemoveCenterOfMassMotion() {
330     return removeCenterOfMassMotion;
331   }
332 
333   /**
334    * If center of mass motion is being removed, then the mean kinetic energy of the system will be 3
335    * kT/2 less than if center of mass motion is allowed.
336    *
337    * @param remove <code>true</code> if center of mass motion is being removed.
338    */
339   public void setRemoveCenterOfMassMotion(boolean remove) {
340     removeCenterOfMassMotion = remove;
341     int nVariables = state.getNumberOfVariables();
342     if (removeCenterOfMassMotion) {
343       degreesOfFreedom = nVariables - 3 - constrainedDoF;
344     } else {
345       degreesOfFreedom = nVariables - constrainedDoF;
346     }
347   }
348 
349   /**
350    * Get the target temperature.
351    *
352    * @return Target temperature.
353    */
354   public double getTargetTemperature() {
355     return targetTemperature;
356   }
357 
358   /**
359    * Set the target temperature.
360    *
361    * @param t Target temperature must be greater than absolute zero.
362    * @since 1.0
363    */
364   public void setTargetTemperature(double t) {
365     // Obey the Third Law of Thermodynamics.
366     assert (t > 0.0);
367     targetTemperature = t;
368     kT = t * kB;
369   }
370 
371   /**
372    * Reset velocities from a Maxwell-Boltzmann distribution of momenta based on the supplied target
373    * temperature. The variance of each independent momentum component is kT * mass.
374    *
375    * @param targetTemperature the target Temperature for the Maxwell distribution.
376    */
377   public void maxwell(double targetTemperature) {
378     if (logger.isLoggable(Level.FINE)) {
379       logger.fine("\n Initializing velocities to target temperature");
380     }
381 
382     setTargetTemperature(targetTemperature);
383 
384     double[] v = state.v();
385     double[] mass = state.getMass();
386     for (int i = 0; i < state.getNumberOfVariables(); i++) {
387       double m = mass[i];
388       if (m > 0.0) {
389         v[i] = random.nextGaussian() * sqrt(kB * targetTemperature / m);
390       }
391     }
392 
393     // Remove the center of mass motion.
394     if (removeCenterOfMassMotion) {
395       centerOfMassMotion(true, !quiet);
396     }
397 
398     // Find the current kinetic energy and temperature.
399     computeKineticEnergy();
400 
401     /*
402      The current temperature will deviate slightly from the target
403      temperature if the center of mass motion was removed and/or due to
404      finite system size.
405 
406      Scale the velocities to enforce the target temperature.
407     */
408     double scale = sqrt(targetTemperature / state.getTemperature());
409     for (int i = 0; i < state.getNumberOfVariables(); i++) {
410       double m = mass[i];
411       if (m > 0.0) {
412         v[i] *= scale;
413       }
414     }
415 
416     // Update the kinetic energy and current temperature.
417     computeKineticEnergy();
418 
419     log(Level.INFO);
420   }
421 
422   /**
423    * Reset velocities from a Maxwell-Boltzmann distribution based on the current target temperature
424    * of thermostat.
425    */
426   public void maxwell() {
427     maxwell(targetTemperature);
428   }
429 
430   /**
431    * Return 3 velocities from a Maxwell-Boltzmann distribution of momenta. The variance of each
432    * independent momentum component is kT * mass.
433    *
434    * @param mass The mass for the degrees of freedom.
435    * @return three velocity components.
436    */
437   public double[] maxwellIndividual(double mass) {
438     double[] vv = new double[3];
439     if (mass > 0.0) {
440       for (int i = 0; i < 3; i++) {
441         vv[i] = random.nextGaussian() * sqrt(kB * targetTemperature / mass);
442       }
443     }
444     return vv;
445   }
446 
447   /**
448    * Setter for the field <code>quiet</code>.
449    *
450    * @param quiet a boolean.
451    */
452   public void setQuiet(boolean quiet) {
453     this.quiet = quiet;
454   }
455 
456   /**
457    * The setRandomSeed method is used to initialize the Random number generator to the same starting
458    * state, such that separate runs produce the same Maxwell-Boltzmann initial velocities. same
459    *
460    * @param seed The seed.
461    */
462   public void setRandomSeed(long seed) {
463     random.setSeed(seed);
464   }
465 
466   /**
467    * {@inheritDoc}
468    */
469   @Override
470   public String toString() {
471     return logTemp();
472   }
473 
474   public String logTemp() {
475     StringBuilder sb = new StringBuilder(
476         format("  Target temperature:           %7.2f Kelvin\n", targetTemperature));
477     sb.append(format("  Current temperature:          %7.2f Kelvin\n", state.getTemperature()));
478     sb.append(format("  Number of variables:          %7d\n", state.getNumberOfVariables()));
479     sb.append(format("  Number of degrees of freedom: %7d\n", degreesOfFreedom));
480     sb.append(format("  Kinetic Energy:               %7.2f\n", state.getKineticEnergy()));
481     sb.append(format("  kT per degree of freedom:     %7.2f",
482         KCAL_TO_GRAM_ANG2_PER_PS2 * state.getKineticEnergy() / (degreesOfFreedom * kT)));
483     return sb.toString();
484   }
485 
486   /**
487    * Log the target temperature and current number of kT per degree of freedom (should be 0.5 kT at
488    * equilibrium).
489    *
490    * @param level a {@link java.util.logging.Level} object.
491    */
492   protected void log(Level level) {
493     if (logger.isLoggable(level) && !quiet) {
494       logger.log(level, logTemp());
495     }
496   }
497 
498   /**
499    * Remove center of mass translational and rotational velocity by inverting the moment of inertia
500    * tensor.
501    *
502    * @param print If true, log removal of center of mass motion.
503    */
504   private void removeCenterOfMassMotion(boolean print) {
505     double xx = 0.0;
506     double yy = 0.0;
507     double zz = 0.0;
508     double xy = 0.0;
509     double xz = 0.0;
510     double yz = 0.0;
511     int index = 0;
512     double[] x = state.x();
513     double[] mass = state.getMass();
514     while (index < state.getNumberOfVariables()) {
515       if (type[index] == VARIABLE_TYPE.OTHER) {
516         index++;
517         continue;
518       }
519       double m = mass[index];
520       assert (type[index] == VARIABLE_TYPE.X);
521       double xi = x[index++] - centerOfMass[0];
522       assert (type[index] == VARIABLE_TYPE.Y);
523       double yi = x[index++] - centerOfMass[1];
524       assert (type[index] == VARIABLE_TYPE.Z);
525       double zi = x[index++] - centerOfMass[2];
526       xx += xi * xi * m;
527       yy += yi * yi * m;
528       zz += zi * zi * m;
529       xy += xi * yi * m;
530       xz += xi * zi * m;
531       yz += yi * zi * m;
532     }
533 
534     //        RealMatrix inertia = new Array2DRowRealMatrix(3, 3);
535     //        inertia.setEntry(0, 0, yy + zz);
536     //        inertia.setEntry(1, 0, -xy);
537     //        inertia.setEntry(2, 0, -xz);
538     //        inertia.setEntry(0, 1, -xy);
539     //        inertia.setEntry(1, 1, xx + zz);
540     //        inertia.setEntry(2, 1, -yz);
541     //        inertia.setEntry(0, 2, -xz);
542     //        inertia.setEntry(1, 2, -yz);
543     //        inertia.setEntry(2, 2, xx + yy);
544     //        inertia = new LUDecomposition(inertia).getSolver().getInverse();
545     //        xx = inertia.getEntry(0, 0);
546     //        yy = inertia.getEntry(1, 1);
547     //        zz = inertia.getEntry(2, 2);
548     //        xy = inertia.getEntry(0, 1);
549     //        xz = inertia.getEntry(0, 2);
550     //        yz = inertia.getEntry(1, 2);
551     //        double ox = angularMomentum[0] * xx + angularMomentum[1] * xy + angularMomentum[2] *
552     // xz;
553     //        double oy = angularMomentum[0] * xy + angularMomentum[1] * yy + angularMomentum[2] *
554     // yz;
555     //        double oz = angularMomentum[0] * xz + angularMomentum[1] * yz + angularMomentum[2] *
556     // zz;
557 
558     // Remove center of mass translational momentum.
559     index = 0;
560     double[] v = state.v();
561     while (index < state.getNumberOfVariables()) {
562       if (type[index] == VARIABLE_TYPE.OTHER) {
563         index++;
564         continue;
565       }
566       double m = mass[index];
567       if (m > 0.0) {
568         v[index++] -= linearMomentum[0] / m;
569         v[index++] -= linearMomentum[1] / m;
570         v[index++] -= linearMomentum[2] / m;
571       } else {
572         index += 3;
573       }
574     }
575 
576     // Only remove center of mass rotational momentum for non-periodic systems.
577     //        if (false) {
578     //            index = 0;
579     //            while (index < nVariables) {
580     //                if (type[index] == VARIABLE_TYPE.OTHER) {
581     //                    index++;
582     //                    continue;
583     //                }
584     //                double xi = x[index++] - centerOfMass[0];
585     //                double yi = x[index++] - centerOfMass[1];
586     //                double zi = x[index] - centerOfMass[2];
587     //                index -= 2;
588     //                v[index++] += (-oy * zi + oz * yi);
589     //                v[index++] += (-oz * xi + ox * zi);
590     //                v[index++] += (-ox * yi + oy * xi);
591     //            }
592     //        }
593 
594     if (print) {
595       logger.info("  Center of mass motion removed.");
596     }
597   }
598 }