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.dynamics;
39  
40  import ffx.numerics.math.RunningStatistics;
41  
42  import java.util.logging.Logger;
43  
44  import static java.lang.String.format;
45  
46  /**
47   * NonEquilbriumDynamics is a class that contains methods to control
48   * non-equilibrium molecular dynamics simulations.
49   */
50  public class NonEquilbriumDynamics {
51  
52    private static final Logger logger = Logger.getLogger(NonEquilbriumDynamics.class.getName());
53  
54    /**
55     * Begin at L=1 and decrease to L=0.
56     */
57    private final boolean reverseNEQ;
58    /**
59     * The number of non-equilibrium lambda steps.
60     */
61    private final int nonEquilibriumLambdaSteps;
62    /**
63     * The non-equilibrium work values.
64     */
65    private final RunningStatistics nonEquilibriumWorkValues;
66    /**
67     * The total number of MD steps.
68     */
69    private long totalMDSteps = 1;
70    /**
71     * The non-equilibrium lambda update frequency.
72     * <p>
73     * The number of MD steps between updates of the non-equilibrium lambda value, which is a function of
74     * the total number of MD times steps and the number of non-equilibrium lambda steps.
75     */
76    private long nonEquilibiumLambdaUpdateFrequency = Long.MAX_VALUE;
77  
78    /**
79     * Constructor for NonEquilbriumDynamics.
80     *
81     * @param nonEquilibriumLambdaSteps The number of non-equilibrium lambda steps.
82     * @param reverseNEQ                If true, lambda values should decrease from 1 to 0.
83     */
84    public NonEquilbriumDynamics(int nonEquilibriumLambdaSteps, boolean reverseNEQ) {
85      if (nonEquilibriumLambdaSteps < 1) {
86        this.nonEquilibriumLambdaSteps = 100;
87      } else {
88        this.nonEquilibriumLambdaSteps = nonEquilibriumLambdaSteps;
89      }
90      this.reverseNEQ = reverseNEQ;
91      nonEquilibriumWorkValues = new RunningStatistics();
92    }
93  
94    /**
95     * Get the number of non-equilibrium lambda steps.
96     *
97     * @return The number of non-equilibrium lambda steps.
98     */
99    public int getNonEquilibriumLambdaSteps() {
100     return nonEquilibriumLambdaSteps;
101   }
102 
103   /**
104    * Get the initial lambda value.
105    * @return The initial lambda value.
106    */
107   public double getInitialLambda() {
108     return reverseNEQ ? 1.0 : 0.0;
109   }
110 
111   /**
112    * Configure increments of the non-equilibrium lambda values based on the total number of MD steps.
113    *
114    * @param nSteps The total number of MD steps.
115    * @return The total number of MD steps may be adjusted to be a multiple of the non-equilibrium lambda steps.
116    */
117   public long setMDSteps(long nSteps) {
118     if (nSteps < 1) {
119       long defaultSteps = 100L * nonEquilibriumLambdaSteps;
120       logger.info(format(" Invalid number of MD steps %d. Setting the number of steps to %d.", nSteps, defaultSteps));
121       nSteps = defaultSteps;
122     }
123 
124     if (nSteps % nonEquilibriumLambdaSteps != 0) {
125       logger.info(format(" Non-equilibrium lambda steps (%d) is not a multiple of total steps (%d).",
126           nonEquilibriumLambdaSteps, nSteps));
127       nSteps = nSteps - (nSteps % nonEquilibriumLambdaSteps);
128       logger.info(format(" Number of steps adjusted to %d.", nSteps));
129     }
130     nonEquilibiumLambdaUpdateFrequency = nSteps / nonEquilibriumLambdaSteps;
131     totalMDSteps = nSteps;
132     return nSteps;
133   }
134 
135   /**
136    * Check if the non-equilibrium lambda value should be updated at a given MD step.
137    *
138    * @param step The MD step number.
139    * @return True if the non-equilibrium lambda value should be updated.
140    */
141   public boolean isUpdateStep(long step) {
142     if (step < 1 || step > totalMDSteps) {
143       logger.severe(format(" Invalid MD step number %d. Must be between 1 and %d.", step, totalMDSteps));
144       return false;
145     }
146     // The last step is a special case.
147     if (step == totalMDSteps) {
148       return true;
149     }
150     return (step - 1) % nonEquilibiumLambdaUpdateFrequency == 0;
151   }
152 
153   /**
154    * Add a work contribution.
155    *
156    * @param work The work value.
157    */
158   public void addWork(double work) {
159     nonEquilibriumWorkValues.addValue(work);
160   }
161 
162   /**
163    * Get the total work for a given range of lambda bins.
164    *
165    * @return The total work.
166    */
167   public double getWork() {
168     return nonEquilibriumWorkValues.getSum();
169   }
170 
171   /**
172    * Get the non-equilibrium lambda value for a given MD step.
173    *
174    * @param step          The MD step number.
175    * @param currentLambda The current lambda value.
176    * @return The lambda value.
177    */
178   public double getNextLambda(long step, double currentLambda) {
179     if (isUpdateStep(step)) {
180       int lambdaBin = getCurrentLambdaBin(step);
181       double lambdaStepSize = 1.0 / nonEquilibriumLambdaSteps;
182       if (reverseNEQ) {
183         return 1.0 - lambdaBin * lambdaStepSize;
184       } else {
185         return lambdaBin * lambdaStepSize;
186       }
187     } else {
188       logger.warning(format(" Non-equilibrium lambda update frequency is %d, but step %d is not a multiple of this frequency.",
189           nonEquilibiumLambdaUpdateFrequency, step - 1));
190       logger.warning(format(" Returning the current lambda value %6.4f.", currentLambda));
191       return currentLambda;
192     }
193   }
194 
195   /**
196    * Get the current lambda bin for a given MD step.
197    *
198    * @param step The MD step number.
199    * @return The lambda bin.
200    */
201   public int getCurrentLambdaBin(long step) {
202     if (step == totalMDSteps) {
203       return nonEquilibriumLambdaSteps;
204     } else if (isUpdateStep(step)) {
205       return (int) ((step - 1) / nonEquilibiumLambdaUpdateFrequency);
206     } else {
207       logger.warning(format(" Non-equilibrium lambda update frequency is %d, but step %d is not a multiple of this frequency.",
208           nonEquilibiumLambdaUpdateFrequency, step - 1));
209       return 0;
210     }
211   }
212 
213 }