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 }