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.numerics.Potential;
41 import ffx.potential.SystemState;
42
43 import java.util.logging.Level;
44 import java.util.logging.Logger;
45
46 import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
47 import static java.lang.String.format;
48
49 /**
50 * Respa performs multiple time step molecular dynamics using the reversible reference system
51 * propagation algorithm (r-RESPA) via a Verlet core with the potential split into fast- and
52 * slow-evolving portions.
53 *
54 * <p>The inner RESPA loop is position Verlet.
55 *
56 * <p>D. D. Humphreys, R. A. Friesner and B. J. Berne, "A Multiple-Time-Step Molecular Dynamics
57 * Algorithm for Macromolecules", Journal of Physical Chemistry, 98, 6885-6892 (1994)
58 *
59 * <p>X. Qian and T. Schlick, "Efficient Multiple-Time-Step Integrators with Distance-Based Force
60 * Splitting for Particle-Mesh-Ewald Molecular Dynamics Simulations", Journal of Chemical Physics,
61 * 115, 4019-4029 (2001)
62 *
63 * @author Gaurav Chattree
64 * @since 1.0
65 */
66 public class Respa extends Integrator {
67
68 private static final Logger logger = Logger.getLogger(Respa.class.getName());
69
70 /**
71 * Number of inner time steps.
72 */
73 private int innerSteps;
74
75 /**
76 * Inner time step in psec.
77 */
78 private double innerTimeStep;
79
80 /**
81 * Half the inner time step.
82 */
83 private double halfInnerTimeStep;
84
85 private double halfStepEnergy = 0;
86
87 private final double[] innerGradient;
88
89 /**
90 * Initialize Respa multiple time step molecular dynamics.
91 *
92 * @param state The molecular dynamics state to be operated on.
93 */
94 public Respa(SystemState state) {
95 super(state);
96 innerGradient = new double[state.getNumberOfVariables()];
97 innerSteps = 4;
98 innerTimeStep = dt / innerSteps;
99 halfInnerTimeStep = 0.5 * innerTimeStep;
100 }
101
102 /**
103 * Get the potential energy of the fast degrees of freedom.
104 *
105 * @return The potential energy of the fast degrees of freedom.
106 */
107 public double getHalfStepEnergy() {
108 return halfStepEnergy;
109 }
110
111 /**
112 * {@inheritDoc}
113 *
114 * <p>The Respa full-step integration operation.
115 */
116 @Override
117 public void postForce(double[] gradient) {
118 double[] a = state.a();
119 double[] v = state.v();
120 double[] mass = state.getMass();
121 for (int i = 0; i < state.getNumberOfVariables(); i++) {
122 double m = mass[i];
123 if (m > 0.0) {
124 a[i] = -KCAL_TO_GRAM_ANG2_PER_PS2 * gradient[i] / m;
125 v[i] += a[i] * dt_2;
126 }
127 }
128 }
129
130 /**
131 * {@inheritDoc}
132 *
133 * <p>Performs the inner RESPA loop via position Verlet.
134 */
135 @Override
136 public void preForce(Potential potential) {
137 int nVariables = state.getNumberOfVariables();
138 double[] x = state.x();
139 double[] v = state.v();
140 double[] a = state.a();
141 double[] aPrevious = state.aPrevious();
142 double[] mass = state.getMass();
143
144 // Find half-step velocities via velocity Verlet recursion
145 for (int i = 0; i < nVariables; i++) {
146 if (mass[i] > 0.0) {
147 v[i] += a[i] * dt_2;
148 }
149 }
150
151 // Initialize accelerations due to fast-evolving forces.
152 potential.setEnergyTermState(Potential.STATE.FAST);
153 halfStepEnergy = potential.energyAndGradient(x, innerGradient);
154 for (int i = 0; i < nVariables; i++) {
155 if (mass[i] > 0.0) {
156 aPrevious[i] = -KCAL_TO_GRAM_ANG2_PER_PS2 * innerGradient[i] / mass[i];
157 }
158 }
159
160 // Complete the inner RESPA loop.
161 for (int j = 0; j < innerSteps; j++) {
162
163 // Find fast-evolving velocities and positions via Verlet recursion.
164 for (int i = 0; i < nVariables; i++) {
165 if (mass[i] > 0.0) {
166 v[i] += aPrevious[i] * halfInnerTimeStep;
167 x[i] += v[i] * innerTimeStep;
168 }
169 }
170
171 // Update accelerations from fast varying forces.
172 halfStepEnergy = potential.energyAndGradient(x, innerGradient);
173 for (int i = 0; i < nVariables; i++) {
174 /*
175 Use Newton's second law to get fast-evolving accelerations.
176 Update fast-evolving velocities using the Verlet recursion.
177 */
178 double m = mass[i];
179 if (m > 0.0) {
180 aPrevious[i] = -KCAL_TO_GRAM_ANG2_PER_PS2 * innerGradient[i] / m;
181 v[i] += aPrevious[i] * halfInnerTimeStep;
182 }
183 }
184 }
185
186 // Revert to computing slowly varying forces.
187 potential.setEnergyTermState(Potential.STATE.SLOW);
188 }
189
190 /**
191 * Set inner Respa number of time steps.
192 *
193 * @param n Number of inner time steps (must be greater than or equal to 2).
194 */
195 public void setInnerTimeSteps(int n) {
196 if (n < 2) {
197 n = 2;
198 }
199
200 innerSteps = n;
201
202 // Update inner time step
203 setTimeStep(dt);
204 }
205
206 /**
207 * {@inheritDoc}
208 *
209 * <p>Set outer Respa time step.
210 */
211 @Override
212 public void setTimeStep(double dt) {
213 if (dt < 0.0005) {
214 dt = 0.0005;
215 }
216
217 this.dt = dt;
218 dt_2 = 0.5 * dt;
219 innerTimeStep = dt / innerSteps;
220 halfInnerTimeStep = 0.5 * innerTimeStep;
221
222 if (logger.isLoggable(Level.FINE)) {
223 logger.fine(
224 format(" Time step set at %f (psec) and inner time step set at %f (psec) \n", this.dt,
225 innerTimeStep));
226 }
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 @Override
233 public String toString() {
234 return "Respa";
235 }
236 }