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.openmm;
39  
40  import com.sun.jna.Pointer;
41  import com.sun.jna.ptr.DoubleByReference;
42  import com.sun.jna.ptr.PointerByReference;
43  import edu.uiowa.jopenmm.OpenMM_Vec3;
44  
45  import java.nio.DoubleBuffer;
46  
47  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_addEnergyParameterDerivative;
48  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_addForce;
49  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_addGlobalParameter;
50  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_addParticle;
51  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_create;
52  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_create_2;
53  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_destroy;
54  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getEnergyFunction;
55  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getEnergyParameterDerivativeName;
56  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getForce;
57  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getGlobalParameterDefaultValue;
58  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getGlobalParameterName;
59  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getNumEnergyParameterDerivatives;
60  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getNumForces;
61  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getNumGlobalParameters;
62  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getNumParticles;
63  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getParticleParameters;
64  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_getPerturbationEnergy;
65  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_setEnergyFunction;
66  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_setGlobalParameterDefaultValue;
67  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_setGlobalParameterName;
68  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_setParticleParameters;
69  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_updateParametersInContext;
70  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_ATMForce_usesPeriodicBoundaryConditions;
71  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_True;
72  
73  /**
74   * The ATMForce class implements the Alchemical Transfer Method (ATM) for OpenMM.
75   * ATM is used to compute the binding free energies of molecular complexes and of other equilibrium processes.
76   * ATM and its implementation are described in the open access article:
77   * <p>
78   * Solmaz Azimi, Sheenam Khuttan, Joe Z. Wu, Rajat K. Pal, and Emilio  Gallicchio.
79   * Relative Binding Free Energy Calculations for Ligands with Diverse Scaffolds with the Alchemical Transfer Method.
80   * J. Chem. Inf. Model.  62, 309 (2022)
81   * https://doi.org/10.1021/acs.jcim.1c01129
82   * <p>
83   * Refer to the publication above for a detailed description of the ATM method and the parameters used in this API
84   * and please cite it to support our work if you use this software in your research.
85   * <p>
86   * The ATMForce implements an arbitrary potential energy function that depends on the potential
87   * energies (u0 and u1) of the system before and after a set of atoms are displaced by a specified amount.
88   * For example, you might displace a molecule from the solvent bulk to a receptor binding site to simulate
89   * a binding process.  The potential energy function typically also depends on one or more parameters that
90   * are dialed to implement alchemical transformations.
91   * <p>
92   * To use this class, create an ATMForce object, passing an algebraic expression to the
93   * constructor that defines the potential energy. This expression can be any combination
94   * of the variables u0 and u1. Then call addGlobalParameter() to define the parameters on which the potential energy expression depends.
95   * The values of global parameters may be modified during a simulation by calling Context::setParameter().
96   * Next, call addForce() to add Force objects that define the terms of the potential energy function
97   * that change upon displacement. Finally, call addParticle() to specify the displacement applied to
98   * each particle. Displacements can be changed by calling setParticleParameters(). As any per-particle parameters,
99   * changes in displacements take effect only after calling updateParametersInContext().
100  * <p>
101  * As an example, the following code creates an ATMForce based on the change in energy of
102  * two particles when the second particle is displaced by 1 nm in the x direction.
103  * The energy change is dialed using an alchemical parameter Lambda, which in this case is set to 1/2:
104  *
105  * <pre>
106  *    ATMForce atmforce = new ATMForce("u0 + Lambda*(u1 - u0)");
107  *    atm.addGlobalParameter("Lambda", 0.5);
108  *    atm.addParticle(new OpenMM_Vec3(0, 0, 0));
109  *    atm.addParticle(new OpenMM_Vec3(1, 0, 0));
110  *    CustomBondForce force = new CustomBondForce("0.5*r^2");
111  *    atm.addForce(force);
112  * </pre>
113  * <p>
114  * Expressions may involve the operators + (add), - (subtract), * (multiply), / (divide), and ^ (power), and the following
115  * functions: sqrt, exp, log, sin, cos, sec, csc, tan, cot, asin, acos, atan, atan2, sinh, cosh, tanh, erf, erfc, min, max, abs, floor, ceil, step, delta,
116  * select.  All trigonometric functions
117  * are defined in radians, and log is the natural logarithm.  step(x) = 0 if x is less than 0, 1 otherwise.  delta(x) = 1 if x is 0, 0 otherwise.
118  * select(x,y,z) = z if x = 0, y otherwise.
119  * <p>
120  * If instead of the energy expression the ATMForce constructor specifies the values of a series of parameters,
121  * the default energy expression is used:
122  *
123  * <pre>
124  *    select(step(Direction), u0, u1) + ((Lambda2-Lambda1)/Alpha)*log(1+exp(-Alpha*(usc-Uh))) + Lambda2*usc + W0;
125  *    usc = select(step(u-Ubcore), (Umax-Ubcore)*fsc+Ubcore, u), u);
126  *    fsc = (z^Acore-1)/(z^Acore+1);
127  *    z = 1 + 2*(y/Acore) + 2*(y/Acore)^2;
128  *    y = (u-Ubcore)/(Umax-Ubcore);
129  *    u = select(step(Direction), 1, -1)*(u1-u0)
130  * </pre>
131  * <p>
132  * which is the same as the soft-core softplus alchemical potential energy function in the Azimi et al. paper above.
133  * <p>
134  * The ATMForce is then added to the System as any other Force
135  *
136  * <pre>
137  *  system.addForce(atmforce);
138  * </pre>
139  * <p>
140  * after which it will be used for energy/force evaluations for molecular dynamics and energy optimization.
141  * You can call getPerturbationEnergy() to query the values of u0 and u1, which are needed for computing
142  * free energies.
143  * <p>
144  * In most cases, particles are only displaced in one of the two states evaluated by this force.  It computes the
145  * change in energy between the current particle coordinates (as stored in the Context) and the displaced coordinates.
146  * In some cases, it is useful to apply displacements to both states.  You can do this by providing two displacement
147  * vectors to addParticle():
148  *
149  * <pre>
150  *    atm.addParticle(new OpenMM_Vec3(1, 0, 0), new OpenMM_Vec3(-1, 0, 0));
151  * </pre>
152  * <p>
153  * In this case, u1 will be computed after displacing the particle in the positive x direction, and
154  * u0 will be computed after displacing it in the negative x direction.
155  * <p>
156  * This class also has the ability to compute derivatives of the potential energy with respect to global parameters.
157  * Call addEnergyParameterDerivative() to request that the derivative with respect to a particular parameter be
158  * computed.  You can then query its value in a Context by calling getState() on it.
159  */
160 public class ATMForce extends Force {
161 
162   /**
163    * Create an ATMForce object.
164    *
165    * @param energy an algebraic expression giving the energy of the system as a function
166    *               of u0 and u1, the energies before and after displacement
167    */
168   public ATMForce(String energy) {
169     super(OpenMM_ATMForce_create(energy));
170   }
171 
172   /**
173    * Create an ATMForce object with the default softplus energy expression.  The values passed to
174    * this constructor are the default values of the global parameters for newly created Contexts.
175    * Their values can be changed by calling setParameter() on the Context using the parameter
176    * names defined by the Lambda1(), Lambda2(), etc. methods below.
177    *
178    * @param lambda1   the default value of the Lambda1 parameter (dimensionless).  This should be
179    *                  a number between 0 and 1.
180    * @param lambda2   the default value of the Lambda2 parameter (dimensionless).  This should be
181    *                  a number between 0 and 1.
182    * @param alpha     the default value of the Alpha parameter (kJ/mol)^-1
183    * @param uh        the default value of the Uh parameter (kJ/mol)
184    * @param w0        the default value of the W0 parameter (kJ/mol)
185    * @param umax      the default value of the Umax parameter (kJ/mol)
186    * @param ubcore    the default value of the Ubcore parameter (kJ/mol)
187    * @param acore     the default value of the Acore parameter dimensionless)
188    * @param direction the default value of the Direction parameter (dimensionless).  This should be
189    *                  either 1 for the forward transfer, or -1 for the backward transfer.
190    */
191   public ATMForce(double lambda1, double lambda2, double alpha, double uh, double w0,
192                   double umax, double ubcore, double acore, double direction) {
193     super(OpenMM_ATMForce_create_2(lambda1, lambda2, alpha, uh, w0, umax, ubcore, acore, direction));
194   }
195 
196   /**
197    * Request that this Force compute the derivative of its energy with respect to a global parameter.
198    * The parameter must have already been added with addGlobalParameter().
199    *
200    * @param name the name of the parameter
201    */
202   public void addEnergyParameterDerivative(String name) {
203     OpenMM_ATMForce_addEnergyParameterDerivative(pointer, name);
204   }
205 
206   /**
207    * Request that this Force compute the derivative of its energy with respect to a global parameter.
208    * The parameter must have already been added with addGlobalParameter().
209    *
210    * @param name the name of the parameter
211    */
212   public void addEnergyParameterDerivative(Pointer name) {
213     OpenMM_ATMForce_addEnergyParameterDerivative(pointer, name);
214   }
215 
216   /**
217    * Add a Force whose energy will be computed by the ATMForce.
218    *
219    * @param force the Force to the be added, which should have been created on the heap with the
220    *              "new" operator.  The ATMForce takes over ownership of it, and deletes the Force when the
221    *              ATMForce itself is deleted.
222    * @return The index within ATMForce of the force that was added
223    */
224   public int addForce(Force force) {
225     return OpenMM_ATMForce_addForce(pointer, force.getPointer());
226   }
227 
228   /**
229    * Add a new global parameter that the interaction may depend on.  The default value provided to
230    * this method is the initial value of the parameter in newly created Contexts.  You can change
231    * the value at any time by calling setParameter() on the Context.
232    *
233    * @param name         the name of the parameter
234    * @param defaultValue the default value of the parameter
235    * @return the index of the parameter that was added
236    */
237   public int addGlobalParameter(String name, double defaultValue) {
238     return OpenMM_ATMForce_addGlobalParameter(pointer, name, defaultValue);
239   }
240 
241   /**
242    * Add a new global parameter that the interaction may depend on.  The default value provided to
243    * this method is the initial value of the parameter in newly created Contexts.  You can change
244    * the value at any time by calling setParameter() on the Context.
245    *
246    * @param name         the name of the parameter
247    * @param defaultValue the default value of the parameter
248    * @return the index of the parameter that was added
249    */
250   public int addGlobalParameter(Pointer name, double defaultValue) {
251     return OpenMM_ATMForce_addGlobalParameter(pointer, name, defaultValue);
252   }
253 
254   /**
255    * Add a particle to the force.
256    * <p>
257    * All of the particles in the System must be added to the ATMForce in the same order
258    * as they appear in the System.
259    *
260    * @param displacement1 the displacement of the particle for the target state in nm
261    * @param displacement0 the displacement of the particle for the initial state in nm
262    * @return the index of the particle that was added
263    */
264   public int addParticle(OpenMM_Vec3 displacement1, OpenMM_Vec3 displacement0) {
265     return OpenMM_ATMForce_addParticle(pointer, displacement1, displacement0);
266   }
267 
268 
269   /**
270    * Destroy the force.
271    */
272   @Override
273   public void destroy() {
274     if (pointer != null) {
275       OpenMM_ATMForce_destroy(pointer);
276       pointer = null;
277     }
278   }
279 
280   /**
281    * Get the energy function.
282    *
283    * @return The energy function.
284    */
285   public String getEnergyFunction() {
286     Pointer p = OpenMM_ATMForce_getEnergyFunction(pointer);
287     if (p == null) {
288       return null;
289     }
290     return p.getString(0);
291   }
292 
293   /**
294    * Get the name of an energy parameter derivative.
295    *
296    * @param index The index of the parameter derivative.
297    * @return The name of the parameter derivative.
298    */
299   public String getEnergyParameterDerivativeName(int index) {
300     Pointer p = OpenMM_ATMForce_getEnergyParameterDerivativeName(pointer, index);
301     if (p == null) {
302       return null;
303     }
304     return p.getString(0);
305   }
306 
307   /**
308    * Get a force by index.
309    *
310    * @param index The index of the force.
311    * @return The force at the specified index.
312    */
313   public PointerByReference getForce(int index) {
314     return OpenMM_ATMForce_getForce(pointer, index);
315   }
316 
317   /**
318    * Get the default value of a global parameter.
319    *
320    * @param index The index of the parameter.
321    * @return The default value of the parameter.
322    */
323   public double getGlobalParameterDefaultValue(int index) {
324     return OpenMM_ATMForce_getGlobalParameterDefaultValue(pointer, index);
325   }
326 
327   /**
328    * Get the name of a global parameter.
329    *
330    * @param index The index of the parameter.
331    * @return The name of the parameter.
332    */
333   public String getGlobalParameterName(int index) {
334     Pointer p = OpenMM_ATMForce_getGlobalParameterName(pointer, index);
335     if (p == null) {
336       return null;
337     }
338     return p.getString(0);
339   }
340 
341   /**
342    * Get the number of energy parameter derivatives.
343    *
344    * @return The number of energy parameter derivatives.
345    */
346   public int getNumEnergyParameterDerivatives() {
347     return OpenMM_ATMForce_getNumEnergyParameterDerivatives(pointer);
348   }
349 
350   /**
351    * Get the number of forces.
352    *
353    * @return The number of forces.
354    */
355   public int getNumForces() {
356     return OpenMM_ATMForce_getNumForces(pointer);
357   }
358 
359   /**
360    * Get the number of global parameters.
361    *
362    * @return The number of global parameters.
363    */
364   public int getNumGlobalParameters() {
365     return OpenMM_ATMForce_getNumGlobalParameters(pointer);
366   }
367 
368   /**
369    * Get the number of particles.
370    *
371    * @return The number of particles.
372    */
373   public int getNumParticles() {
374     return OpenMM_ATMForce_getNumParticles(pointer);
375   }
376 
377   /**
378    * Get the parameters for a particle
379    *
380    * @param index         the index in the force for the particle for which to get parameters
381    * @param displacement1 the displacement of the particle for the target state in nm
382    * @param displacement0 the displacement of the particle for the initial state in nm
383    */
384   public void getParticleParameters(int index, OpenMM_Vec3 displacement1, OpenMM_Vec3 displacement0) {
385     OpenMM_ATMForce_getParticleParameters(pointer, index, displacement1, displacement0);
386   }
387 
388   /**
389    * Returns the current perturbation energy.
390    *
391    * @param context the Context for which to return the energy
392    * @param u1      on exit, the energy of the displaced state
393    * @param u0      on exit, the energy of the non-displaced state
394    * @param energy  on exit, the value of this force's energy function
395    */
396   public void getPerturbationEnergy(Context context, DoubleByReference u0, DoubleByReference u1, DoubleByReference energy) {
397     OpenMM_ATMForce_getPerturbationEnergy(pointer, context.getPointer(), u0, u1, energy);
398   }
399 
400   /**
401    * Returns the current perturbation energy.
402    *
403    * @param context the Context for which to return the energy
404    * @param u1      on exit, the energy of the displaced state
405    * @param u0      on exit, the energy of the non-displaced state
406    * @param energy  on exit, the value of this force's energy function
407    */
408   public void getPerturbationEnergy(Context context, DoubleBuffer u0, DoubleBuffer u1, DoubleBuffer energy) {
409     OpenMM_ATMForce_getPerturbationEnergy(pointer, context.getPointer(), u0, u1, energy);
410   }
411 
412 
413   /**
414    * Set the energy function.
415    *
416    * @param energy The energy function.
417    */
418   public void setEnergyFunction(String energy) {
419     OpenMM_ATMForce_setEnergyFunction(pointer, energy);
420   }
421 
422   /**
423    * Set the energy function.
424    *
425    * @param energy The energy function.
426    */
427   public void setEnergyFunction(Pointer energy) {
428     OpenMM_ATMForce_setEnergyFunction(pointer, energy);
429   }
430 
431   /**
432    * Set the default value of a global parameter.
433    *
434    * @param index        The index of the parameter.
435    * @param defaultValue The default value of the parameter.
436    */
437   public void setGlobalParameterDefaultValue(int index, double defaultValue) {
438     OpenMM_ATMForce_setGlobalParameterDefaultValue(pointer, index, defaultValue);
439   }
440 
441   /**
442    * Set the name of a global parameter.
443    *
444    * @param index The index of the parameter.
445    * @param name  The name of the parameter.
446    */
447   public void setGlobalParameterName(int index, String name) {
448     OpenMM_ATMForce_setGlobalParameterName(pointer, index, name);
449   }
450 
451   /**
452    * Set the name of a global parameter.
453    *
454    * @param index The index of the parameter.
455    * @param name  The name of the parameter.
456    */
457   public void setGlobalParameterName(int index, Pointer name) {
458     OpenMM_ATMForce_setGlobalParameterName(pointer, index, name);
459   }
460 
461   /**
462    * Set the parameters for a particle
463    *
464    * @param index         the index in the force of the particle for which to set parameters
465    * @param displacement1 the displacement of the particle for the target state in nm
466    * @param displacement0 the displacement of the particle for the initial state in nm
467    */
468   public void setParticleParameters(int index, OpenMM_Vec3 displacement1, OpenMM_Vec3 displacement0) {
469     OpenMM_ATMForce_setParticleParameters(pointer, index, displacement1, displacement0);
470   }
471 
472   /**
473    * Update the per-particle parameters in a Context to match those stored in this Force object.  This method
474    * should be called after updating parameters with setParticleParameters() to copy them over to the Context.
475    * The only information this method updates is the values of per-particle parameters.  The number of particles
476    * cannot be changed.
477    */
478   public void updateParametersInContext(Context context) {
479     if (context.hasContextPointer()) {
480       OpenMM_ATMForce_updateParametersInContext(pointer, context.getPointer());
481     }
482   }
483 
484   /**
485    * Returns whether or not this force makes use of periodic boundary conditions.
486    */
487   @Override
488   public boolean usesPeriodicBoundaryConditions() {
489     int pbc = OpenMM_ATMForce_usesPeriodicBoundaryConditions(pointer);
490     return pbc == OpenMM_True;
491   }
492 }