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.potential.openmm;
39
40 import com.sun.jna.ptr.PointerByReference;
41 import ffx.openmm.State;
42 import ffx.potential.bonded.Atom;
43 import ffx.potential.utils.EnergyException;
44
45 import javax.annotation.Nullable;
46 import java.util.Arrays;
47
48 import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_AngstromsPerNm;
49 import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_KcalPerKJ;
50 import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
51 import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Energy;
52 import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Forces;
53 import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Positions;
54 import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Velocities;
55 import static java.lang.Double.isInfinite;
56 import static java.lang.Double.isNaN;
57 import static java.lang.String.format;
58
59 /**
60 * Retrieve state information from an OpenMM Simulation.
61 */
62 public class OpenMMState extends State {
63
64 /**
65 * Potential energy (kcal/mol).
66 */
67 public final double potentialEnergy;
68 /**
69 * Kinetic energy (kcal/mol).
70 */
71 public final double kineticEnergy;
72 /**
73 * Total energy (kcal/mol).
74 */
75 public final double totalEnergy;
76 /**
77 * Mask of information to retrieve.
78 */
79 private final int dataTypes;
80
81 /**
82 * Construct an OpenMM State with the requested information.
83 *
84 * @param pointer Pointer to an OpenMM state.
85 */
86 protected OpenMMState(PointerByReference pointer) {
87 super(pointer);
88
89 // Set the data types mask using the super class method.
90 this.dataTypes = super.getDataTypes();
91 if (stateContains(OpenMM_State_Energy)) {
92 // Set the energy fields using the super class method and convert units.
93 potentialEnergy = super.getPotentialEnergy() * OpenMM_KcalPerKJ;
94 kineticEnergy = super.getKineticEnergy() * OpenMM_KcalPerKJ;
95 totalEnergy = potentialEnergy + kineticEnergy;
96 } else {
97 potentialEnergy = 0.0;
98 kineticEnergy = 0.0;
99 totalEnergy = 0.0;
100 }
101 }
102
103 /**
104 * The acceleration array will contain the acceleration information for all atoms. This
105 * method will convert the OpenMM forces to accelerations using the atom masses.
106 *
107 * @param a Acceleration components for all atoms.
108 * @param atoms The array of atoms, which is needed to convert from force to acceleration.
109 * @return The acceleration for each atom in units of Angstroms per picosecond squared.
110 */
111 public double[] getAccelerations(@Nullable double[] a, Atom[] atoms) {
112 // Check if the state contains forces.
113 if (!stateContains(OpenMM_State_Forces)) {
114 return a;
115 }
116 double[] forces = getForces();
117 int n = forces.length;
118
119 // Validate the atoms array exists and is not empty.
120 if (atoms == null || atoms.length == 0) {
121 throw new IllegalArgumentException("Atoms array must not be null or empty.");
122 }
123 // Validate the number of degrees of freedom.
124 if (atoms.length * 3 != n) {
125 String message = format(" The number of atoms (%d) does not match the number of degrees of freedom (%d).", atoms.length, n);
126 throw new IllegalArgumentException(message);
127 }
128 if (a == null || a.length != n) {
129 a = new double[n];
130 }
131
132 int index = 0;
133 for (Atom atom : atoms) {
134 double mass = atom.getMass();
135 double xx = forces[index] * OpenMM_AngstromsPerNm / mass;
136 double yy = forces[index + 1] * OpenMM_AngstromsPerNm / mass;
137 double zz = forces[index + 2] * OpenMM_AngstromsPerNm / mass;
138 a[index] = xx;
139 a[index + 1] = yy;
140 a[index + 2] = zz;
141 index += 3;
142 }
143 return a;
144 }
145
146 /**
147 * The acceleration array will contain the acceleration information for all atoms. This
148 * method will convert the OpenMM forces to accelerations using the atom masses.
149 *
150 * @param a Acceleration components for all atoms.
151 * @param atoms The array of atoms, which is needed to convert from force to acceleration.
152 * @return The acceleration for each atom in units of Angstroms per picosecond squared.
153 */
154 public double[] getActiveAccelerations(@Nullable double[] a, Atom[] atoms) {
155 if (!stateContains(OpenMM_State_Forces)) {
156 return a;
157 }
158 return filterToActive(getAccelerations(null, atoms), a, atoms);
159 }
160
161 /**
162 * The force array contains the OpenMM force information for all atoms.
163 *
164 * @param g Gradient array to use.
165 * @return The gradient for all atoms in units of kcal/mol/Angstrom (in a new array if g is null or the wrong size).
166 */
167 public double[] getGradient(@Nullable double[] g) {
168 // Check if the state contains forces.
169 if (!stateContains(OpenMM_State_Forces)) {
170 return g;
171 }
172 double[] forces = getForces();
173 int n = forces.length;
174
175 // Validate the gradient array exists and is the correct size.
176 if (g == null || g.length != n) {
177 g = new double[n];
178 }
179
180 for (int i = 0; i < n; i++) {
181 double xx = -forces[i] * OpenMM_NmPerAngstrom * OpenMM_KcalPerKJ;
182 if (isNaN(xx) || isInfinite(xx)) {
183 throw new EnergyException(
184 format(" The gradient of degree of freedom %d is %8.3f.", i, xx));
185 }
186 g[i] = xx;
187 }
188 return g;
189 }
190
191 /**
192 * The force array contains the OpenMM force information for active atoms.
193 *
194 * @param g Gradient array to use.
195 * @return The gradient for all atoms in units of kcal/mol/Angstrom (in a new array if g is null or the wrong size).
196 */
197 public double[] getActiveGradient(@Nullable double[] g, Atom[] atoms) {
198 if (!stateContains(OpenMM_State_Forces)) {
199 return g;
200 }
201 return filterToActive(getGradient(null), g, atoms);
202 }
203
204 /**
205 * Read the periodic lattice vectors from a state.
206 *
207 * <p>The crystal instance will be updated, and passed to the ForceFieldEnergy instance.
208 */
209 public double[][] getPeriodicBoxVectors() {
210 if (!stateContains(OpenMM_State_Positions)) {
211 return null;
212 }
213
214 double[][] latticeVectors = super.getPeriodicBoxVectors();
215 latticeVectors[0][0] *= OpenMM_AngstromsPerNm;
216 latticeVectors[0][1] *= OpenMM_AngstromsPerNm;
217 latticeVectors[0][2] *= OpenMM_AngstromsPerNm;
218 latticeVectors[1][0] *= OpenMM_AngstromsPerNm;
219 latticeVectors[1][1] *= OpenMM_AngstromsPerNm;
220 latticeVectors[1][2] *= OpenMM_AngstromsPerNm;
221 latticeVectors[2][0] *= OpenMM_AngstromsPerNm;
222 latticeVectors[2][1] *= OpenMM_AngstromsPerNm;
223 latticeVectors[2][2] *= OpenMM_AngstromsPerNm;
224 return latticeVectors;
225 }
226
227 /**
228 * The position array contains the OpenMM atomic position information for all atoms. The
229 * returned array x is in units of Angstroms.
230 *
231 * @param x Atomic coordinates array to use.
232 * @return The atomic coordinates (in a new array if x is null or the wrong size).
233 */
234 public double[] getPositions(@Nullable double[] x) {
235 // Check if the state contains positions.
236 if (!stateContains(OpenMM_State_Positions)) {
237 return x;
238 }
239
240 double[] pos = getPositions();
241 int n = pos.length;
242
243 // Allocate x if null or the wrong size.
244 if (x == null || x.length != n) {
245 x = new double[n];
246 }
247
248 for (int i = 0; i < n; i++) {
249 x[i] = pos[i] * OpenMM_AngstromsPerNm;
250 }
251
252 return x;
253 }
254
255 /**
256 * The position array contains the OpenMM atomic position information for active atoms.
257 * The returned array x is in units of Angstroms.
258 *
259 * @param x Atomic coordinates array to use.
260 * @return The atomic coordinates (in a new array if x is null or the wrong size).
261 */
262 public double[] getActivePositions(@Nullable double[] x, Atom[] atoms) {
263 if (!stateContains(OpenMM_State_Positions)) {
264 return x;
265 }
266 return filterToActive(getPositions(null), x, atoms);
267 }
268
269 /**
270 * The velocity array contains the OpenMM atomic position information for all atoms. The
271 * returned array v is in units of Angstroms per picosecond.
272 *
273 * @param v Atomic velocity array to use.
274 * @return The atomic velocities (in a new array if v is null or the wrong size).
275 */
276 public double[] getVelocities(@Nullable double[] v) {
277 if (!stateContains(OpenMM_State_Velocities)) {
278 return v;
279 }
280
281 double[] vel = getVelocities();
282 int n = vel.length;
283
284 // Validate the velocity array exists and is the correct size.
285 if (v == null || v.length != n) {
286 v = new double[n];
287 }
288
289 for (int i = 0; i < n; i++) {
290 v[i] = vel[i] * OpenMM_AngstromsPerNm;
291 }
292
293 return v;
294 }
295
296 /**
297 * The velocity array contains the OpenMM atomic position information for active atoms.
298 * The returned array v is in units of Angstroms per picosecond.
299 *
300 * @param v Atomic velocity array to use.
301 * @return The atomic velocities (in a new array if v is null or the wrong size).
302 */
303 public double[] getActiveVelocities(@Nullable double[] v, Atom[] atoms) {
304 if (!stateContains(OpenMM_State_Velocities)) {
305 return v;
306 }
307 return filterToActive(getVelocities(null), v, atoms);
308 }
309
310 /**
311 * Get the periodic box volume.
312 *
313 * @return The periodic box volume.
314 */
315 @Override
316 public double getPeriodicBoxVolume() {
317 return super.getPeriodicBoxVolume()
318 * OpenMM_AngstromsPerNm * OpenMM_AngstromsPerNm * OpenMM_AngstromsPerNm;
319 }
320
321 /**
322 * Get the potential energy. This field will be zero if the dataTypes mask did not include the energy.
323 *
324 * @return The potential energy.
325 */
326 @Override
327 public double getPotentialEnergy() {
328 return potentialEnergy;
329 }
330
331 /**
332 * Get the kinetic energy. This field will be zero if the dataTypes mask did not include the energy.
333 *
334 * @return The kinetic energy.
335 */
336 @Override
337 public double getKineticEnergy() {
338 return kineticEnergy;
339 }
340
341 /**
342 * Get the total energy. This field will be zero if the dataTypes mask did not include the energy.
343 *
344 * @return The total energy.
345 */
346 public double getTotalEnergy() {
347 return totalEnergy;
348 }
349
350 /**
351 * Get the mask of information contained in the state.
352 *
353 * @return The mask of information contained in the state.
354 */
355 @Override
356 public int getDataTypes() {
357 return dataTypes;
358 }
359
360 /**
361 * Check to see if the state contains the requested information.
362 *
363 * @param dataType Information to check for.
364 * @return boolean indicating whether the state contains the requested information.
365 */
366 private boolean stateContains(int dataType) {
367 return (dataTypes & dataType) == dataType;
368 }
369
370 /**
371 * Filter an array to only include elements where the corresponding Atom is active.
372 *
373 * @param source The source array to filter.
374 * @param target The target array to fill with filtered values (or null to create a new one).
375 * @param atoms The array of Atoms, which should have a corresponding active mask.
376 */
377 private static double[] filterToActive(double[] source, @Nullable double[] target, Atom[] atoms) {
378 if (source == null || atoms == null) {
379 throw new IllegalArgumentException("The arrays must be non-null.");
380 }
381
382 // Validate that the source array length is three times the number of atoms.
383 if (source.length != atoms.length * 3) {
384 throw new IllegalArgumentException("Source array length must be three times the number of atoms.");
385 }
386
387 // Count the number of active atoms.
388 int count = (int) Arrays.stream(atoms).filter(Atom::isActive).count();
389
390 // Ensure target is large enough to hold the filtered values.
391 if (target == null || target.length < count * 3) {
392 target = new double[count * 3];
393 }
394
395 // Fill the target array with values from the source array for active atoms.
396 int sourceIndedx = 0;
397 int targetIndex = 0;
398 for (Atom atom : atoms) {
399 if (atom.isActive()) {
400 target[targetIndex] = source[sourceIndedx];
401 target[targetIndex + 1] = source[sourceIndedx + 1];
402 target[targetIndex + 2] = source[sourceIndedx + 2];
403 targetIndex += 3;
404 }
405 // Always increment the source index by 3 for each atom.
406 sourceIndedx += 3;
407 }
408 return target;
409 }
410 }