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 }