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.nonbonded.pme;
39  
40  import static java.lang.String.format;
41  import static org.apache.commons.math3.util.FastMath.pow;
42  
43  import edu.rit.pj.IntegerSchedule;
44  import edu.rit.util.Range;
45  import ffx.crystal.Crystal;
46  import ffx.potential.parameters.ForceField;
47  import java.util.logging.Logger;
48  
49  public class AlchemicalParameters {
50  
51    private static final Logger logger = Logger.getLogger(AlchemicalParameters.class.getName());
52  
53    private final Polarization polarization;
54  
55    /** Constant α in: r' = sqrt(r^2 + α*(1 - L)^2) */
56    public double permLambdaAlpha = 1.0;
57    /** Power on L in front of the pairwise multipole potential. */
58    public double permLambdaExponent = 3.0;
59    /** Begin turning on permanent multipoles at Lambda = 0.4; */
60    public double permLambdaStart = 0.4;
61    /** Finish turning on permanent multipoles at Lambda = 1.0; */
62    public double permLambdaEnd = 1.0;
63    /**
64     * Start turning on polarization later in the Lambda path to prevent SCF convergence problems when
65     * atoms nearly overlap.
66     */
67    public double polLambdaStart = 0.75;
68    public double polLambdaEnd = 1.0;
69    /** Power on L in front of the polarization energy. */
70    public double polLambdaExponent = 3.0;
71    /** Intramolecular electrostatics for the ligand in vapor is included by default. */
72    public boolean doLigandVaporElec = true;
73    /** Intramolecular electrostatics for the ligand in done in GK implicit solvent. */
74    public boolean doLigandGKElec = false;
75    /**
76     * Condensed phase SCF without the ligand present is included by default. For DualTopologyEnergy
77     * calculations it can be turned off.
78     */
79    public boolean doNoLigandCondensedSCF = true;
80    /** lAlpha = α*(1 - L)^2 */
81    public double lAlpha = 0.0;
82    public double dlAlpha = 0.0;
83    public double d2lAlpha = 0.0;
84    public double dEdLSign = 1.0;
85    /** lPowPerm = L^permanentLambdaExponent */
86    public double lPowPerm = 1.0;
87    public double dlPowPerm = 0.0;
88    public double d2lPowPerm = 0.0;
89    public boolean doPermanentRealSpace = true;
90    public double permanentScale = 1.0;
91    /** lPowPol = L^polarizationLambdaExponent */
92    public double lPowPol = 1.0;
93    public double dlPowPol = 0.0;
94    public double d2lPowPol = 0.0;
95    public boolean doPolarization = true;
96    /**
97     * When computing the polarization energy at L there are 3 pieces.
98     *
99     * <p>1.) Upol(1) = The polarization energy computed normally (ie. system with ligand).
100    *
101    * <p>2.) Uenv = The polarization energy of the system without the ligand.
102    *
103    * <p>3.) Uligand = The polarization energy of the ligand by itself.
104    *
105    * <p>Upol(L) = L*Upol(1) + (1-L)*(Uenv + Uligand)
106    *
107    * <p>Set polarizationScale to L for part 1. Set polarizationScale to (1-L) for parts 2 and 3.
108    */
109   public double polarizationScale = 1.0;
110   /**
111    * The polarization Lambda value goes from 0.0 .. 1.0 as the global lambda value varies between
112    * polLambdaStart .. polLambadEnd.
113    */
114   public double polLambda = 1.0;
115   /**
116    * The permanent Lambda value goes from 0.0 .. 1.0 as the global lambda value varies between
117    * permLambdaStart .. permLambdaEnd.
118    */
119   public double permLambda = 1.0;
120   /** Boundary conditions for the vapor end of the alchemical path. */
121   public Crystal vaporCrystal = null;
122   public int[][][] vaporLists = null;
123   public Range[] vacuumRanges = null;
124   public IntegerSchedule vaporPermanentSchedule = null;
125   public IntegerSchedule vaporEwaldSchedule = null;
126 
127   public AlchemicalParameters(ForceField forceField, boolean lambdaTerm,
128       boolean nnTerm, Polarization polarization) {
129     this.polarization = polarization;
130     if (lambdaTerm) {
131       // Values of PERMANENT_LAMBDA_ALPHA below 2 can lead to unstable  trajectories.
132       permLambdaAlpha = forceField.getDouble("PERMANENT_LAMBDA_ALPHA", 2.0);
133       if (permLambdaAlpha < 0.0 || permLambdaAlpha > 3.0) {
134         logger.warning("Invalid value for permanent-lambda-alpha (<0.0 || >3.0); reverting to 2.0");
135         permLambdaAlpha = 2.0;
136       }
137 
138       /*
139        A PERMANENT_LAMBDA_EXPONENT of 2 gives a non-zero d2U/dL2 at the
140        beginning of the permanent schedule. Choosing a power of 3 or
141        greater ensures a smooth dU/dL and d2U/dL2 over the schedule.
142 
143        A value of 0.0 is also admissible for when ExtendedSystem is
144        scaling multipoles rather than softcoring them.
145       */
146       permLambdaExponent = forceField.getDouble("PERMANENT_LAMBDA_EXPONENT", 3.0);
147       if (permLambdaExponent < 0.0) {
148         logger.warning("Invalid value for permanent-lambda-exponent (<0.0); reverting to 3.0");
149         permLambdaExponent = 3.0;
150       }
151 
152       /*
153        A POLARIZATION_LAMBDA_EXPONENT of 2 gives a non-zero d2U/dL2 at
154        the beginning of the polarization schedule. Choosing a power of 3
155        or greater ensures a smooth dU/dL and d2U/dL2 over the schedule.
156 
157        A value of 0.0 is also admissible: when polarization is not being
158        softcored but instead scaled, as by ExtendedSystem.
159       */
160       polLambdaExponent = forceField.getDouble("POLARIZATION_LAMBDA_EXPONENT", 3.0);
161       if (polLambdaExponent < 0.0) {
162         logger.warning("Invalid value for polarization-lambda-exponent (<0.0); reverting to 3.0");
163         polLambdaExponent = 3.0;
164       }
165 
166       // Values of PERMANENT_LAMBDA_START below 0.5 can lead to unstable trajectories.
167       permLambdaStart = forceField.getDouble("PERMANENT_LAMBDA_START", 0.4);
168       if (permLambdaStart < 0.0 || permLambdaStart > 1.0) {
169         logger.warning("Invalid value for perm-lambda-start (<0.0 || >1.0); reverting to 0.4");
170         permLambdaStart = 0.4;
171       }
172 
173       // Values of PERMANENT_LAMBDA_END must be greater than permLambdaStart and <= 1.0.
174       permLambdaEnd = forceField.getDouble("PERMANENT_LAMBDA_END", 1.0);
175       if (permLambdaEnd < permLambdaStart || permLambdaEnd > 1.0) {
176         logger.warning("Invalid value for perm-lambda-end (<start || >1.0); reverting to 1.0");
177         permLambdaEnd = 1.0;
178       }
179 
180         /*
181          The POLARIZATION_LAMBDA_START defines the point in the lambda
182          schedule when the condensed phase polarization of the ligand
183          begins to be turned on. If the condensed phase polarization
184          is considered near lambda=0, then SCF convergence is slow,
185          even with Thole damping. In addition, 2 (instead of 1)
186          condensed phase SCF calculations are necessary from the
187          beginning of the window to lambda=1.
188         */
189       polLambdaStart = forceField.getDouble("POLARIZATION_LAMBDA_START", 0.75);
190       if (polLambdaStart < 0.0 || polLambdaStart > 1.0) {
191         logger.warning("Invalid value for polarization-lambda-start; reverting to 0.75");
192         polLambdaStart = 0.75;
193       }
194 
195         /*
196          The POLARIZATION_LAMBDA_END defines the point in the lambda
197          schedule when the condensed phase polarization of ligand has
198          been completely turned on. Values other than 1.0 have not been tested.
199         */
200       polLambdaEnd = forceField.getDouble("POLARIZATION_LAMBDA_END", 1.0);
201       if (polLambdaEnd < polLambdaStart || polLambdaEnd > 1.0) {
202         logger.warning(
203             "Invalid value for polarization-lambda-end (<start || >1.0); reverting to 1.0");
204         polLambdaEnd = 1.0;
205       }
206 
207       // The LAMBDA_VAPOR_ELEC defines if intramolecular electrostatics of the ligand in vapor
208       // will be considered.
209       doLigandVaporElec = forceField.getBoolean("LIGAND_VAPOR_ELEC", true);
210       doLigandGKElec = forceField.getBoolean("LIGAND_GK_ELEC", false);
211       doNoLigandCondensedSCF = forceField.getBoolean("NO_LIGAND_CONDENSED_SCF", true);
212     } else if (nnTerm) {
213       permLambdaAlpha = 0.0;
214       permLambdaExponent = 1.0;
215       polLambdaExponent = 1.0;
216       permLambdaStart = 0.0;
217       permLambdaEnd = 1.0;
218       polLambdaStart = 0.0;
219       polLambdaEnd = 1.0;
220       // The LAMBDA_VAPOR_ELEC defines if intramolecular electrostatics of the neural network
221       // atoms in vapor will be removed.
222       doLigandVaporElec = forceField.getBoolean("LIGAND_VAPOR_ELEC", true);
223       doLigandGKElec = false;
224       doNoLigandCondensedSCF = false;
225     }
226   }
227 
228   public String toString() {
229     StringBuilder sb = new StringBuilder("   Alchemical Parameters\n");
230     sb.append(
231         format(
232             "    Permanent Multipole Range:      %5.3f-%5.3f\n", permLambdaStart, permLambdaEnd));
233     sb.append(format("    Permanent Multipole Softcore Alpha:   %5.3f\n", permLambdaAlpha));
234     sb.append(format("    Permanent Multipole Lambda Exponent:  %5.3f\n", permLambdaExponent));
235     if (polarization != Polarization.NONE) {
236       sb.append(format("    Polarization Lambda Exponent:         %5.3f\n", polLambdaExponent));
237       sb.append(
238           format(
239               "    Polarization Range:             %5.3f-%5.3f\n", polLambdaStart, polLambdaEnd));
240       sb.append(format("    Condensed SCF Without Ligand:         %B\n", doNoLigandCondensedSCF));
241     }
242     if (!doLigandGKElec) {
243       sb.append(format("    Vapor Electrostatics:                 %B\n", doLigandVaporElec));
244     } else {
245       sb.append(format("    GK Electrostatics at L=0:             %B\n", doLigandGKElec));
246     }
247     return sb.toString();
248   }
249 
250   /*
251    * f = sqrt(r^2 + lAlpha)
252    *
253    * df/dL = -alpha * (1.0 - lambda) / f
254    *
255    * g = 1 / sqrt(r^2 + lAlpha)
256    *
257    * dg/dL = alpha * (1.0 - lambda) / (r^2 + lAlpha)^(3/2)
258    *
259    * define dlAlpha = alpha * 1.0 - lambda)
260    *
261    * then df/dL = -dlAlpha / f and dg/dL = dlAlpha * g^3
262    *
263    * Multipoles are turned on from permLambdaStart .. permLambdaEnd.
264    *
265    * @param lambda
266    */
267   public void update(double lambda) {
268 
269     lPowPerm = 1.0;
270     dlPowPerm = 0.0;
271     d2lPowPerm = 0.0;
272     lAlpha = 0.0;
273     dlAlpha = 0.0;
274     d2lAlpha = 0.0;
275     if (lambda < permLambdaStart) {
276       lPowPerm = 0.0;
277     } else if (lambda <= permLambdaEnd) {
278       double permWindow = permLambdaEnd - permLambdaStart;
279       double permLambdaScale = 1.0 / permWindow;
280       permLambda = permLambdaScale * (lambda - permLambdaStart);
281 
282       lAlpha = permLambdaAlpha * (1.0 - permLambda) * (1.0 - permLambda);
283       dlAlpha = permLambdaAlpha * (1.0 - permLambda);
284       d2lAlpha = -permLambdaAlpha;
285 
286       lPowPerm = pow(permLambda, permLambdaExponent);
287       dlPowPerm = permLambdaExponent * pow(permLambda, permLambdaExponent - 1.0);
288       d2lPowPerm = 0.0;
289       if (permLambdaExponent >= 2.0) {
290         d2lPowPerm =
291             permLambdaExponent
292                 * (permLambdaExponent - 1.0)
293                 * pow(permLambda, permLambdaExponent - 2.0);
294       }
295 
296       dlAlpha *= permLambdaScale;
297       d2lAlpha *= (permLambdaScale * permLambdaScale);
298       dlPowPerm *= permLambdaScale;
299       d2lPowPerm *= (permLambdaScale * permLambdaScale);
300     }
301 
302     // Polarization is turned on from polarizationLambdaStart .. polarizationLambdaEnd.
303     lPowPol = 1.0;
304     dlPowPol = 0.0;
305     d2lPowPol = 0.0;
306     if (lambda < polLambdaStart) {
307       lPowPol = 0.0;
308     } else if (lambda <= polLambdaEnd) {
309       double polWindow = polLambdaEnd - polLambdaStart;
310       double polLambdaScale = 1.0 / polWindow;
311       polLambda = polLambdaScale * (lambda - polLambdaStart);
312       if (polLambdaExponent > 0.0) {
313         lPowPol = pow(polLambda, polLambdaExponent);
314         if (polLambdaExponent >= 1.0) {
315           dlPowPol = polLambdaExponent * pow(polLambda, polLambdaExponent - 1.0);
316           if (polLambdaExponent >= 2.0) {
317             d2lPowPol =
318                 polLambdaExponent
319                     * (polLambdaExponent - 1.0)
320                     * pow(polLambda, polLambdaExponent - 2.0);
321           }
322         }
323       }
324       // Add the chain rule term due to shrinking the lambda range for the polarization energy.
325       dlPowPol *= polLambdaScale;
326       d2lPowPol *= (polLambdaScale * polLambdaScale);
327     }
328   }
329 }