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