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-2024.
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 com.sun.jna.ptr.DoubleByReference;
41  import com.sun.jna.ptr.IntByReference;
42  import ffx.crystal.Crystal;
43  import ffx.openmm.CustomBondForce;
44  import ffx.openmm.CustomNonbondedForce;
45  import ffx.openmm.DoubleArray;
46  import ffx.openmm.IntSet;
47  import ffx.potential.bonded.Atom;
48  import ffx.potential.nonbonded.NonbondedCutoff;
49  import ffx.potential.nonbonded.VanDerWaals;
50  import ffx.potential.nonbonded.VanDerWaalsForm;
51  import ffx.potential.parameters.ForceField;
52  
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  
56  import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
57  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_Boolean.OpenMM_True;
58  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_CustomNonbondedForce_NonbondedMethod.OpenMM_CustomNonbondedForce_CutoffPeriodic;
59  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_CustomNonbondedForce_NonbondedMethod.OpenMM_CustomNonbondedForce_NoCutoff;
60  import static ffx.potential.nonbonded.VanDerWaalsForm.EPSILON_RULE.GEOMETRIC;
61  import static ffx.potential.nonbonded.VanDerWaalsForm.RADIUS_RULE.ARITHMETIC;
62  import static ffx.potential.nonbonded.VanDerWaalsForm.VDW_TYPE.LENNARD_JONES;
63  import static java.lang.String.format;
64  
65  /**
66   * Fixed Charge Alchemical Forces.
67   * <p>
68   * 1. Handle interactions between non-alchemical atoms with our default OpenMM NonBondedForce.
69   * Note that alchemical atoms must have eps=0 to turn them off in this force.
70   * <p>
71   * 2. Handle interactions between alchemical atoms and mixed non-alchemical <-> alchemical
72   * interactions with an OpenMM CustomNonBondedForce.
73   */
74  public class FixedChargeAlchemicalForces {
75  
76    private static final Logger logger = Logger.getLogger(RestrainPositionsForce.class.getName());
77  
78    private CustomNonbondedForce fixedChargeSoftcoreForce;
79  
80    private CustomBondForce alchemicalAlchemicalStericsForce;
81  
82    private CustomBondForce nonAlchemicalAlchemicalStericsForce;
83  
84    public FixedChargeAlchemicalForces(OpenMMEnergy openMMEnergy,
85                                       FixedChargeNonbondedForce fixedChargeNonBondedForce) {
86  
87      VanDerWaals vdW = openMMEnergy.getVdwNode();
88      if (vdW == null) {
89        return;
90      }
91  
92      /*
93       Only 6-12 LJ with arithmetic mean to define sigma and geometric mean
94       for epsilon is supported.
95      */
96      VanDerWaalsForm vdwForm = vdW.getVDWForm();
97      if (vdwForm.vdwType != LENNARD_JONES || vdwForm.radiusRule != ARITHMETIC
98          || vdwForm.epsilonRule != GEOMETRIC) {
99        logger.info(format(" VDW Type:         %s", vdwForm.vdwType));
100       logger.info(format(" VDW Radius Rule:  %s", vdwForm.radiusRule));
101       logger.info(format(" VDW Epsilon Rule: %s", vdwForm.epsilonRule));
102       logger.log(Level.SEVERE, " Unsupported van der Waals functional form.");
103       return;
104     }
105 
106     // Sterics mixing rules.
107     String stericsMixingRules = " epsilon = sqrt(epsilon1*epsilon2);";
108     stericsMixingRules += " rmin = 0.5 * (sigma1 + sigma2) * 1.122462048309372981;";
109 
110     // Softcore Lennard-Jones, with a form equivalent to that used in FFX VanDerWaals class.
111     String stericsEnergyExpression = "(vdw_lambda^beta)*epsilon*x*(x-2.0);";
112     // Effective softcore distance for sterics.
113     stericsEnergyExpression += " x = 1.0 / (alpha*(1.0-vdw_lambda)^2.0 + (r/rmin)^6.0);";
114     // Define energy expression for sterics.
115     String energyExpression = stericsEnergyExpression + stericsMixingRules;
116 
117     fixedChargeSoftcoreForce = new CustomNonbondedForce(energyExpression);
118 
119     // Get the Alpha and Beta constants from the VanDerWaals instance.
120     OpenMMSystem openMMSystem = openMMEnergy.getSystem();
121 
122     double alpha = openMMSystem.getVdWSoftcoreAlpha();
123     double beta = openMMSystem.getVdwSoftcorePower();
124 
125     fixedChargeSoftcoreForce.addGlobalParameter("vdw_lambda", 1.0);
126     fixedChargeSoftcoreForce.addGlobalParameter("alpha", alpha);
127     fixedChargeSoftcoreForce.addGlobalParameter("beta", beta);
128     fixedChargeSoftcoreForce.addPerParticleParameter("sigma");
129     fixedChargeSoftcoreForce.addPerParticleParameter("epsilon");
130 
131     // Add particles.
132     IntSet alchemicalGroup = new IntSet();
133     IntSet nonAlchemicalGroup = new IntSet();
134     DoubleByReference charge = new DoubleByReference();
135     DoubleByReference sigma = new DoubleByReference();
136     DoubleByReference eps = new DoubleByReference();
137 
138     int index = 0;
139     DoubleArray parameters = new DoubleArray(0);
140     Atom[] atoms = openMMEnergy.getMolecularAssembly().getAtomArray();
141     for (Atom atom : atoms) {
142       if (atom.applyLambda()) {
143         alchemicalGroup.insert(index);
144       } else {
145         nonAlchemicalGroup.insert(index);
146       }
147 
148       fixedChargeNonBondedForce.getParticleParameters(index, charge, sigma, eps);
149       double sigmaValue = sigma.getValue();
150       double epsValue = eps.getValue();
151 
152       // Handle cases where sigma is 0.0; for example Amber99 tyrosine hydrogen atoms.
153       if (sigmaValue == 0.0) {
154         sigmaValue = 1.0;
155         epsValue = 0.0;
156       }
157 
158       parameters.append(sigmaValue);
159       parameters.append(epsValue);
160       fixedChargeSoftcoreForce.addParticle(parameters);
161       parameters.resize(0);
162       index++;
163     }
164     parameters.destroy();
165     fixedChargeSoftcoreForce.addInteractionGroup(alchemicalGroup, alchemicalGroup);
166     fixedChargeSoftcoreForce.addInteractionGroup(alchemicalGroup, nonAlchemicalGroup);
167     alchemicalGroup.destroy();
168     nonAlchemicalGroup.destroy();
169 
170     Crystal crystal = openMMEnergy.getCrystal();
171     if (crystal.aperiodic()) {
172       fixedChargeSoftcoreForce.setNonbondedMethod(OpenMM_CustomNonbondedForce_NoCutoff);
173     } else {
174       fixedChargeSoftcoreForce.setNonbondedMethod(OpenMM_CustomNonbondedForce_CutoffPeriodic);
175     }
176 
177     NonbondedCutoff nonbondedCutoff = vdW.getNonbondedCutoff();
178     double off = nonbondedCutoff.off;
179     double cut = nonbondedCutoff.cut;
180     if (cut == off) {
181       logger.warning(" OpenMM does not properly handle cutoffs where cut == off!");
182       if (cut == Double.MAX_VALUE || cut == Double.POSITIVE_INFINITY) {
183         logger.info(" Detected infinite or max-value cutoff; setting cut to 1E+40 for OpenMM.");
184         cut = 1E40;
185       } else {
186         logger.info(format(" Detected cut %8.4g == off %8.4g; scaling cut to 0.99 of off for OpenMM.", cut, off));
187         cut *= 0.99;
188       }
189     }
190     fixedChargeSoftcoreForce.setCutoffDistance(OpenMM_NmPerAngstrom * off);
191     fixedChargeSoftcoreForce.setUseSwitchingFunction(OpenMM_True);
192     fixedChargeSoftcoreForce.setSwitchingDistance(OpenMM_NmPerAngstrom * cut);
193 
194     ForceField forceField = openMMEnergy.getMolecularAssembly().getForceField();
195     int forceGroup = forceField.getInteger("VDW_FORCE_GROUP", 1);
196     fixedChargeSoftcoreForce.setForceGroup(forceGroup);
197 
198     // Alchemical with Alchemical could be either softcore or normal interactions (softcore here).
199     alchemicalAlchemicalStericsForce = new CustomBondForce(stericsEnergyExpression);
200 
201     // Non-Alchemical with Alchemical is essentially always softcore.
202     nonAlchemicalAlchemicalStericsForce = new CustomBondForce(stericsEnergyExpression);
203 
204     // Currently both are treated the same (so we could condense the code below).
205     alchemicalAlchemicalStericsForce.addPerBondParameter("rmin");
206     alchemicalAlchemicalStericsForce.addPerBondParameter("epsilon");
207     alchemicalAlchemicalStericsForce.addGlobalParameter("vdw_lambda", 1.0);
208     alchemicalAlchemicalStericsForce.addGlobalParameter("alpha", alpha);
209     alchemicalAlchemicalStericsForce.addGlobalParameter("beta", beta);
210 
211     nonAlchemicalAlchemicalStericsForce.addPerBondParameter("rmin");
212     nonAlchemicalAlchemicalStericsForce.addPerBondParameter("epsilon");
213     nonAlchemicalAlchemicalStericsForce.addGlobalParameter("vdw_lambda", 1.0);
214     nonAlchemicalAlchemicalStericsForce.addGlobalParameter("alpha", alpha);
215     nonAlchemicalAlchemicalStericsForce.addGlobalParameter("beta", beta);
216 
217     int range = fixedChargeNonBondedForce.getNumExceptions();
218 
219     IntByReference atomi = new IntByReference();
220     IntByReference atomj = new IntByReference();
221     int[][] torsionMask = vdW.getMask14();
222 
223     for (int i = 0; i < range; i++) {
224       fixedChargeNonBondedForce.getExceptionParameters(i, atomi, atomj, charge, sigma, eps);
225 
226       // Omit both Exclusions (1-2, 1-3) and Exceptions (scaled 1-4) from the
227       // CustomNonbondedForce.
228       fixedChargeSoftcoreForce.addExclusion(atomi.getValue(), atomj.getValue());
229 
230       // Deal with scaled 1-4 torsions using the CustomBondForce
231       int[] maskI = torsionMask[atomi.getValue()];
232       int jID = atomj.getValue();
233       boolean epsException = false;
234 
235       for (int mask : maskI) {
236         if (mask == jID) {
237           epsException = true;
238           break;
239         }
240       }
241 
242       if (epsException) {
243         Atom atom1 = atoms[atomi.getValue()];
244         Atom atom2 = atoms[atomj.getValue()];
245         boolean bothAlchemical = false;
246         boolean oneAlchemical = false;
247         if (atom1.applyLambda() && atom2.applyLambda()) {
248           bothAlchemical = true;
249         } else if ((atom1.applyLambda() && !atom2.applyLambda()) || (!atom1.applyLambda() && atom2.applyLambda())) {
250           oneAlchemical = true;
251         }
252         if (bothAlchemical || oneAlchemical) {
253           DoubleArray bondParameters = new DoubleArray(0);
254           bondParameters.append(sigma.getValue() * 1.122462048309372981);
255           bondParameters.append(eps.getValue());
256           if (bothAlchemical) {
257             alchemicalAlchemicalStericsForce.addBond(atomi.getValue(), atomj.getValue(), bondParameters);
258           } else {
259             nonAlchemicalAlchemicalStericsForce.addBond(atomi.getValue(), atomj.getValue(), bondParameters);
260           }
261           bondParameters.destroy();
262         }
263       }
264     }
265     alchemicalAlchemicalStericsForce.setForceGroup(forceGroup);
266     nonAlchemicalAlchemicalStericsForce.setForceGroup(forceGroup);
267     logger.log(Level.INFO, format("  Added fixed charge softcore force \t%d", forceGroup));
268     logger.log(Level.INFO, format("   Alpha = %8.6f and beta = %8.6f", alpha, beta));
269   }
270 
271   public CustomNonbondedForce getFixedChargeSoftcoreForce() {
272     return fixedChargeSoftcoreForce;
273   }
274 
275   public CustomBondForce getAlchemicalAlchemicalStericsForce() {
276     return alchemicalAlchemicalStericsForce;
277   }
278 
279   public CustomBondForce getNonAlchemicalAlchemicalStericsForce() {
280     return nonAlchemicalAlchemicalStericsForce;
281   }
282 
283 }