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.algorithms.commands.test;
39  
40  import ffx.algorithms.cli.AlgorithmsCommand;
41  import ffx.utilities.FFXBinding;
42  import org.apache.commons.math3.special.Erf;
43  import picocli.CommandLine.Command;
44  import picocli.CommandLine.Option;
45  import picocli.CommandLine.Parameters;
46  
47  import java.util.List;
48  
49  import static java.lang.Math.PI;
50  import static java.lang.Math.exp;
51  import static java.lang.Math.pow;
52  import static java.lang.Math.sqrt;
53  import static java.lang.String.format;
54  
55  /**
56   * The Freefix script calculates analytical free energy, entropy, and enthalpy corrections
57   * for flat-bottom harmonic restraints with inner and outer radii.
58   * <br>
59   * Usage:
60   * <br>
61   * ffxc test.Freefix [options] &lt;filename&gt;
62   */
63  @Command(description = " Calculate restraint free energy corrections.", name = "test.Freefix")
64  public class Freefix extends AlgorithmsCommand {
65  
66    /**
67     * --ri Constant Inner Radius value.
68     */
69    @Option(names = {"--ri"}, paramLabel = "0.00", defaultValue = "0.00",
70        description = "Constant Inner Radius value.")
71    private double innerRadius;
72  
73    /**
74     * --fi Constant Inner Force value.
75     */
76    @Option(names = {"--fi"}, paramLabel = "0.0", defaultValue = "0.0",
77        description = "Constant Inner Force value.")
78    private double innerForce;
79  
80    /**
81     * --ro Constant Outer Radius value.
82     */
83    @Option(names = {"--ro"}, paramLabel = "0.00", defaultValue = "0.00",
84        description = "Constant Outer Radius value.")
85    private double outerRadius;
86  
87    /**
88     * --fo Constant Outer Force value.
89     */
90    @Option(names = {"--fo"}, paramLabel = "15.0", defaultValue = "15.0",
91        description = "Constant Outer Force value.")
92    private double outerForce;
93  
94    /**
95     * --temp Constant Temperature value.
96     */
97    @Option(names = {"--temp"}, paramLabel = "298.0", defaultValue = "298.0",
98        description = "Constant Temperature value.")
99    private double temperature;
100 
101   /**
102    * The final argument(s) should be one or more filenames.
103    */
104   @Parameters(arity = "1..*", paramLabel = "files",
105       description = "Atomic coordinate files in PDB or XYZ format.")
106   private List<String> filenames;
107 
108   public static final double AVOGADRO = 6.02214076e23;
109   public static final double STD_CONVERSION = 1.0e27 / AVOGADRO;
110 
111   /**
112    * Freefix Constructor.
113    */
114   public Freefix() {
115     super();
116   }
117 
118   /**
119    * Freefix Constructor.
120    * @param binding The Binding to use.
121    */
122   public Freefix(FFXBinding binding) {
123     super(binding);
124   }
125 
126   /**
127    * Freefix constructor that sets the command line arguments.
128    * @param args Command line arguments.
129    */
130   public Freefix(String[] args) {
131     super(args);
132   }
133 
134   /**
135    * {@inheritDoc}
136    */
137   @Override
138   public Freefix run() {
139 
140     // Init the context and bind variables.
141     if (!init()) {
142       return this;
143     }
144 
145     if (innerForce == 0.0) {
146       innerForce = 1.0;
147       innerRadius = 0.0;
148     }
149     if (outerForce == 0.0) {
150       outerForce = 1.0;
151     }
152 
153     System.out.printf("%-35s %.4f Ang%n", "Inner Flat-Bottom Radius:", innerRadius);
154     System.out.printf("%-35s %.4f Kcal/mole/Ang^2%n", "Inner Force Constant:", innerForce);
155     System.out.printf("%-35s %.4f Ang%n", "Outer Flat-Bottom Radius:", outerRadius);
156     System.out.printf("%-35s %.4f Kcal/mole/Ang^2%n", "Outer Force Constant:", outerForce);
157     System.out.printf("%-35s %.4f Kelvin%n%n", "System Temperature Value:", temperature);
158 
159     double kB = 0.0019872041;
160     double kt = kB * temperature;
161 
162     double[] volumeResults = computeVolumeIntegrals(innerRadius, innerForce, outerRadius, outerForce, temperature);
163     double vol = volumeResults[0];
164     double dvol = volumeResults[1];
165 
166     logger.info(
167         format("%-35s %.4f Ang^3%n", "Analytical Volume Integral:", vol));
168     logger.info(
169         format("%-35s %.4f Ang^3/K%n", "Analytical dVol/dT Value:", dvol));
170 
171     double dg = -kt * Math.log(vol / STD_CONVERSION);
172     double ds = -dg / temperature + kt * dvol / vol;
173     double dh = dg + temperature * ds;
174 
175     logger.info(
176         format("%-35s %.4f Kcal/mole%n", "Restraint Free Energy:", dg));
177     logger.info(
178         format("%-35s %.4f Kcal/mole/K%n", "Restraint Entropy Value:", ds));
179     logger.info(
180         format("%-35s %.4f Kcal/mole%n", "Restraint Enthalpy Value:", dh));
181     logger.info(
182         format("%-35s %.4f Kcal/mole%n", "Restraint -T deltaS Value:", -temperature * ds));
183 
184     return this;
185   }
186 
187   /**
188    * Compute volume integrals and their temperature derivatives for restraint thermodynamics.
189    *
190    * @param innerRadius Inner flat-bottom radius
191    * @param innerForce Inner force constant
192    * @param outerRadius Outer flat-bottom radius
193    * @param outerForce Outer force constant
194    * @param temperature System temperature
195    * @return Array containing [volume, dvolume/dT]
196    */
197   public static double[] computeVolumeIntegrals(double innerRadius, double innerForce,
198       double outerRadius, double outerForce, double temperature) {
199     double kB = 0.0019872041;
200     double kt = kB * temperature;
201     double volume1 = 0.0, volume2, volume3 = 0.0;
202 
203     if (innerRadius != 0.0) {
204       double term1_v1 = 2.0 * PI * innerRadius * (-2.0 + exp(-pow(innerRadius, 2) * innerForce / kt)) * kt / innerForce;
205       double term2_v1 = sqrt(kt * pow(PI / innerForce, 3)) *
206           (2.0 * innerForce * pow(innerRadius, 2) + kt) * Erf.erf(innerRadius * sqrt(innerForce / kt));
207       volume1 = term1_v1 + term2_v1;
208     }
209 
210     volume2 = (4.0 / 3.0) * PI * (pow(outerRadius, 3) - pow(innerRadius, 3));
211 
212     if (outerRadius == 0.0) {
213       double term1_v3 = sqrt(kt * pow(PI / outerForce, 3)) * kt;
214       volume3 = term1_v3;
215     } else {
216       double term1_v3 = sqrt(kt * pow(PI / outerForce, 3)) *
217           (2.0 * outerForce * pow(outerRadius, 2) + kt + 4.0 * outerRadius * sqrt(kt * outerForce / PI));
218       volume3 = term1_v3;
219     }
220 
221     double volume = volume1 + volume2 + volume3;
222 
223     double dv1 = (innerRadius != 0.0) ?
224         computeDv1(innerRadius, innerForce, kt, temperature) : 0.0;
225     double dv3 = (outerRadius != 0.0) ?
226         computeDv3(outerRadius, outerForce, kt, temperature) :
227         1.5 * pow(kt * PI / outerForce, 1.5) / temperature;
228 
229     double dvolume = dv1 + dv3;
230     return new double[]{volume, dvolume};
231   }
232 
233   /**
234    * Compute temperature derivative of inner volume integral.
235    *
236    * @param innerRadius Inner flat-bottom radius
237    * @param innerForce Inner force constant
238    * @param kt Boltzmann constant times temperature
239    * @param temperature System temperature
240    * @return Temperature derivative of inner volume
241    */
242   private static double computeDv1(double innerRadius, double innerForce, double kt, double temperature) {
243     double term1_dv1 = 2.0 * PI * pow(innerRadius, 3) * exp(-pow(innerRadius, 2) * innerForce / kt) / temperature;
244     double term2_dv1 = 2.0 * PI * innerRadius * (-2.0 + exp(-pow(innerRadius, 2) * innerForce / kt)) * kt / (innerForce * temperature);
245     double term3_dv1 = 0.5 * sqrt(pow(PI / innerForce, 3)) * sqrt(kt) *
246         (2.0 * innerForce * pow(innerRadius, 2) + kt) * Erf.erf(innerRadius * sqrt(innerForce / kt)) / temperature;
247     double term4_dv1 = -PI * innerRadius * exp(-pow(innerRadius, 2) * innerForce / kt) *
248         (2.0 * innerForce * pow(innerRadius, 2) + kt) / (innerForce * temperature);
249     double term5_dv1 = sqrt(pow(kt * PI / innerForce, 3)) * Erf.erf(innerRadius * sqrt(innerForce / kt)) / temperature;
250     return term1_dv1 + term2_dv1 + term3_dv1 + term4_dv1 + term5_dv1;
251   }
252 
253   /**
254    * Compute temperature derivative of outer volume integral.
255    *
256    * @param outerRadius Outer flat-bottom radius
257    * @param outerForce Outer force constant
258    * @param kt Boltzmann constant times temperature
259    * @param temperature System temperature
260    * @return Temperature derivative of outer volume
261    */
262   private static double computeDv3(double outerRadius, double outerForce, double kt, double temperature) {
263     double term1_dv3 = sqrt(kt * pow(PI / outerForce, 3)) * outerForce * pow(outerRadius, 2) / temperature;
264     double term2_dv3 = 4.0 * kt * (PI / outerForce) * outerRadius / temperature;
265     double term3_dv3 = 1.5 * sqrt(pow(kt * PI / outerForce, 3)) / temperature;
266     return term1_dv3 + term2_dv3 + term3_dv3;
267   }
268 }