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.potential.openmm;
39  
40  import ffx.crystal.Crystal;
41  import ffx.numerics.switching.UnivariateSwitchingFunction;
42  import ffx.potential.DualTopologyEnergy;
43  import ffx.potential.ForceFieldEnergy;
44  import ffx.potential.MolecularAssembly;
45  import ffx.potential.Platform;
46  import ffx.potential.bonded.Atom;
47  import ffx.potential.parameters.ForceField;
48  import ffx.potential.utils.EnergyException;
49  
50  import javax.annotation.Nullable;
51  import java.util.logging.Level;
52  import java.util.logging.Logger;
53  
54  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_True;
55  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Energy;
56  import static java.lang.Double.isFinite;
57  import static java.lang.String.format;
58  
59  public class OpenMMDualTopologyEnergy extends DualTopologyEnergy implements OpenMMPotential {
60  
61    private static final Logger logger = Logger.getLogger(OpenMMDualTopologyEnergy.class.getName());
62  
63    /**
64     * OpenMM Context.
65     */
66    private final OpenMMContext openMMContext;
67    /**
68     * OpenMMDualTopologySystem.
69     */
70    private final OpenMMDualTopologySystem openMMDualTopologySystem;
71    /**
72     * The atoms this OpenMMEnergy operates on.
73     */
74    private final Atom[] atoms;
75    /**
76     * MolecularAssembly for topology 1.
77     */
78    private final MolecularAssembly molecularAssembly1;
79    /**
80     * MolecularAssembly for topology 2.
81     */
82    private final MolecularAssembly molecularAssembly2;
83  
84    /**
85     * Constructor for DualTopologyEnergy.
86     *
87     * @param topology1         a {@link MolecularAssembly} object.
88     * @param topology2         a {@link MolecularAssembly} object.
89     * @param switchFunction    a {@link UnivariateSwitchingFunction} object.
90     * @param requestedPlatform a {@link Platform} object.
91     */
92    public OpenMMDualTopologyEnergy(MolecularAssembly topology1, MolecularAssembly topology2,
93                                    UnivariateSwitchingFunction switchFunction, Platform requestedPlatform) {
94      super(topology1, topology2, switchFunction);
95  
96      logger.info("\n Initializing an OpenMM Dual Topology System.");
97  
98      molecularAssembly1 = topology1;
99      molecularAssembly2 = topology2;
100 
101     // Check that the two topologies do not use symmetry operators.
102     Crystal crystal1 = molecularAssembly1.getCrystal();
103     int symOps1 = crystal1.spaceGroup.getNumberOfSymOps();
104     Crystal crystal2 = molecularAssembly2.getCrystal();
105     int symOps2 = crystal2.spaceGroup.getNumberOfSymOps();
106     if (symOps1 > 1 || symOps2 > 1) {
107       logger.severe(" OpenMM does not support symmetry operators.");
108     }
109 
110     // Load the OpenMM plugins
111     ForceField forceField = topology1.getForceField();
112     ffx.openmm.Platform openMMPlatform = OpenMMContext.loadPlatform(requestedPlatform, forceField);
113 
114     // Create the OpenMM System.
115     openMMDualTopologySystem = new OpenMMDualTopologySystem(this);
116     openMMDualTopologySystem.addForces();
117 
118     atoms = getDualTopologyAtoms(0);
119 
120     // Create the Context.
121     openMMContext = new OpenMMContext(openMMPlatform, openMMDualTopologySystem, atoms);
122   }
123 
124 
125   /**
126    * {@inheritDoc}
127    */
128   @Override
129   public double energy(double[] x) {
130     return energy(x, false);
131   }
132 
133   /**
134    * {@inheritDoc}
135    */
136   @Override
137   public double energy(double[] x, boolean verbose) {
138     // Make sure the context has been created.
139     openMMContext.update();
140 
141     updateParameters(atoms);
142 
143     // Unscale and set the coordinates.
144     unscaleCoordinates(x);
145     setCoordinates(x);
146 
147     OpenMMState openMMState = openMMContext.getOpenMMState(OpenMM_State_Energy);
148     double e = openMMState.potentialEnergy;
149     openMMState.destroy();
150 
151     if (!isFinite(e)) {
152       String message = format(" OpenMMDualTopologyEnergy was a non-finite %8g", e);
153       logger.warning(message);
154       throw new EnergyException(message);
155     }
156 
157     if (verbose) {
158       logger.log(Level.INFO, format("\n OpenMM Energy: %14.10g", e));
159     }
160 
161     return e;
162   }
163 
164   /**
165    * Compute the energy using the pure Java code path.
166    *
167    * @param x Atomic coordinates.
168    * @return The energy (kcal/mol)
169    */
170   public double energyFFX(double[] x) {
171     return super.energy(x, false);
172   }
173 
174   /**
175    * Compute the energy using the pure Java code path.
176    *
177    * @param x       Input atomic coordinates
178    * @param verbose Use verbose logging.
179    * @return The energy (kcal/mol)
180    */
181   public double energyFFX(double[] x, boolean verbose) {
182     return super.energy(x, verbose);
183   }
184 
185 
186   /**
187    * Coordinates for active atoms in units of Angstroms.
188    *
189    * @param x Atomic coordinates active atoms.
190    */
191   @Override
192   public void setCoordinates(double[] x) {
193     // Load the coordinates for active atoms.
194     super.setCoordinates(x);
195 
196     int n = atoms.length * 3;
197     double[] xall = new double[n];
198     int i = 0;
199     for (Atom atom : atoms) {
200       xall[i] = atom.getX();
201       xall[i + 1] = atom.getY();
202       xall[i + 2] = atom.getZ();
203       i += 3;
204     }
205 
206     // Load OpenMM coordinates for all atoms.
207     openMMContext.setPositions(xall);
208   }
209 
210   /**
211    * Velocities for active atoms in units of Angstroms.
212    *
213    * @param v Velocities for active atoms.
214    */
215   @Override
216   public void setVelocity(double[] v) {
217     // Load the velocity for active atoms.
218     super.setVelocity(v);
219 
220     int n = atoms.length * 3;
221     double[] vall = new double[n];
222     double[] v3 = new double[3];
223     int i = 0;
224     for (Atom atom : atoms) {
225       atom.getVelocity(v3);
226       // If the atom is not active, set its velocity to zero.
227       if (!atom.isActive()) {
228         v3[0] = 0.0;
229         v3[1] = 0.0;
230         v3[2] = 0.0;
231         atom.setVelocity(v3);
232       }
233       vall[i] = v3[0];
234       vall[i + 1] = v3[1];
235       vall[i + 2] = v3[2];
236       i += 3;
237     }
238 
239     // Load OpenMM velocities for all atoms.
240     openMMContext.setVelocities(vall);
241   }
242 
243 
244   /**
245    * Get the MolecularAssembly.
246    *
247    * @param topology The topology index (0 for topology 1, 1 for topology 2).
248    * @return a {@link MolecularAssembly} object for topology 1.
249    */
250   public MolecularAssembly getMolecularAssembly(int topology) {
251     if (topology == 0) {
252       return molecularAssembly1;
253     } else if (topology == 1) {
254       return molecularAssembly2;
255     } else {
256       throw new IllegalArgumentException(" Invalid topology index: " + topology);
257     }
258   }
259 
260   /**
261    * Get the OpenMMEnergy for the specified topology.
262    *
263    * @param topology The topology index (0 for topology 1, 1 for topology 2).
264    * @return The OpenMMEnergy for the specified topology.
265    */
266   public ForceFieldEnergy getForceFieldEnergy(int topology) {
267     if (topology == 0) {
268       return getForceFieldEnergy1();
269     } else if (topology == 1) {
270       return getForceFieldEnergy2();
271     } else {
272       throw new IllegalArgumentException(" Invalid topology index: " + topology);
273     }
274   }
275 
276   /**
277    * Update parameters if the Use flags and/or Lambda value has changed.
278    *
279    * @param atoms Atoms in this list are considered.
280    */
281   @Override
282   public void updateParameters(@Nullable Atom[] atoms) {
283     if (atoms == null) {
284       atoms = this.atoms;
285     }
286     if (openMMDualTopologySystem != null) {
287       openMMDualTopologySystem.updateParameters(atoms);
288     }
289   }
290 
291   /**
292    * Returns the Context instance.
293    *
294    * @return context
295    */
296   @Override
297   public OpenMMContext getContext() {
298     return openMMContext;
299   }
300 
301   /**
302    * Create an OpenMM Context.
303    *
304    * <p>Context.free() must be called to free OpenMM memory.
305    *
306    * @param integratorName Integrator to use.
307    * @param timeStep       Time step.
308    * @param temperature    Temperature (K).
309    * @param forceCreation  Force a new Context to be created, even if the existing one matches the
310    *                       request.
311    */
312   @Override
313   public void updateContext(String integratorName, double timeStep, double temperature, boolean forceCreation) {
314     openMMContext.update(integratorName, timeStep, temperature, forceCreation);
315   }
316 
317   /**
318    * Create an immutable OpenMM State.
319    *
320    * <p>State.free() must be called to free OpenMM memory.
321    *
322    * @param mask The State mask.
323    * @return Returns the State.
324    */
325   @Override
326   public OpenMMState getOpenMMState(int mask) {
327     return openMMContext.getOpenMMState(mask);
328   }
329 
330   /**
331    * Get a reference to the System instance.
332    *
333    * @return a reference to the OpenMMSystem.
334    */
335   @Override
336   public OpenMMSystem getSystem() {
337     return openMMDualTopologySystem;
338   }
339 
340   /**
341    * Update active atoms.
342    */
343   @Override
344   public boolean setActiveAtoms() {
345     return openMMDualTopologySystem.updateAtomMass();
346   }
347 
348 }