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.mc;
39
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Random;
43 import java.util.logging.Logger;
44
45 import static ffx.utilities.Constants.R;
46 import static java.lang.Double.isFinite;
47 import static java.lang.String.format;
48 import static org.apache.commons.math3.util.FastMath.exp;
49 import static org.apache.commons.math3.util.FastMath.min;
50
51 /**
52 * The BoltzmannMC abstract class is a skeleton for Boltzmann-weighted Metropolis Monte Carlo
53 * simulations.
54 *
55 * @author Michael J. Schnieders
56 * @author Jacob M. Litman
57 */
58 public abstract class BoltzmannMC implements MetropolisMC {
59
60 /** Constant <code>logger</code> */
61 private static final Logger logger = Logger.getLogger(BoltzmannMC.class.getName());
62
63 protected final Random random = new Random();
64 /**
65 * Room temperature in Kelvin.
66 */
67 private double temperature = 298.15;
68 /**
69 * Constant factor for Monte Carlo moves: 1.0 / (kB * T)
70 */
71 private double invKBT = 1.0 / (R * temperature);
72
73 private boolean print = true;
74 private double e1 = 0.0;
75 private double e2 = 0.0;
76 private double lastE = 0.0;
77 private boolean lastAccept = false;
78
79 /**
80 * Boltzmann-weighted acceptance probability
81 *
82 * @param invKT 1.0 / (Boltzmann constant * temperature)
83 * @param e1 Energy before move
84 * @param e2 Proposed energy
85 * @return Chance of accepting this move
86 */
87 public static double acceptChance(double invKT, double e1, double e2) {
88 double dE = e2 - e1;
89 return min(exp(-invKT * dE), 1.0);
90 }
91
92 /**
93 * Boltzmann-weighted acceptance probability
94 *
95 * @param random Source of randomness
96 * @param invKT 1.0 / (Boltzmann constant * temperature)
97 * @param e1 Energy before move
98 * @param e2 Proposed energy
99 * @return Whether to accept the move.
100 */
101 public static boolean evaluateMove(Random random, double invKT, double e1, double e2) {
102 boolean e1Finite = isFinite(e1);
103 boolean e2Finite = isFinite(e2);
104 if (!e1Finite && !e2Finite) {
105 throw new IllegalArgumentException(
106 format(" Attempting to evaluate " + "move with non-finite energies %f and %f!", e1, e2));
107 } else if (!e1Finite) {
108 throw new IllegalArgumentException(
109 format(" Attempting to evaluate move " + "from non-finite %f to finite %.4f!", e1, e2));
110 } else if (!e2Finite) {
111 throw new IllegalArgumentException(
112 format(" Attempting to evaluate move " + "from finite %.4f to non-finite %.4f!", e1, e2));
113 }
114 if (e2 <= e1) {
115 return true;
116 } else {
117 // p = exp(-dE / kB*T)
118 double prob = acceptChance(invKT, e1, e2);
119 assert (prob >= 0.0
120 && prob <= 1.0) : "The probability of an MC move up in energy should be between [0 ..1].";
121 double trial = random.nextDouble();
122 return (trial <= prob);
123 }
124 }
125
126 /**
127 * {@inheritDoc}
128 *
129 * <p>Criterion for accept/reject a move; intended to be used mostly internally.
130 */
131 @Override
132 public boolean evaluateMove(double e1, double e2) {
133 return evaluateMove(random, invKBT, e1, e2);
134 }
135
136 /** {@inheritDoc} */
137 @Override
138 public boolean getAccept() {
139 return lastAccept;
140 }
141
142 /** {@inheritDoc} */
143 @Override
144 public double getE1() {
145 return e1;
146 }
147
148 /** {@inheritDoc} */
149 @Override
150 public double getE2() {
151 return e2;
152 }
153
154 /** {@inheritDoc} */
155 @Override
156 public double getTemperature() {
157 return temperature;
158 }
159
160 /** {@inheritDoc} */
161 @Override
162 public void setTemperature(double temp) {
163 temperature = temp;
164 invKBT = 1.0 / (R * temperature);
165 }
166
167 /** {@inheritDoc} */
168 @Override
169 public double lastEnergy() {
170 return lastE;
171 }
172
173 /** {@inheritDoc} */
174 @Override
175 public boolean mcStep(MCMove move) {
176 return mcStep(move, currentEnergy());
177 }
178
179 /** {@inheritDoc} */
180 @Override
181 public boolean mcStep(MCMove move, double en1) {
182 List<MCMove> moveList = new ArrayList<>(1);
183 moveList.add(move);
184 return mcStep(moveList, en1);
185 }
186
187 /** {@inheritDoc} */
188 @Override
189 public boolean mcStep(List<MCMove> moves) {
190 return mcStep(moves, currentEnergy());
191 }
192
193 /**
194 * {@inheritDoc}
195 *
196 * <p>Performs a Boltzmann-weighted Monte Carlo step with an arbitrary list of moves and a defined
197 * starting energy. The list of MCMoves should be of a type with O(1) element access, as the
198 * current implementation utilizes an indexed for loop.
199 */
200 @Override
201 public boolean mcStep(List<MCMove> moves, double en1) {
202 storeState();
203 e1 = en1;
204
205 int nMoves = moves.size();
206 for (MCMove move : moves) {
207 move.move();
208 }
209
210 lastE = currentEnergy(); // Is reset to e1 if move rejected.
211 e2 = lastE;
212 if (evaluateMove(e1, e2)) {
213 lastAccept = true;
214 if (print) {
215 logger.info(format(" Monte Carlo step accepted: e1 -> e2 %10.6f -> %10.6f", e1, e2));
216 }
217 return true;
218 } else {
219 lastAccept = false;
220 for (int i = nMoves - 1; i >= 0; i--) {
221 moves.get(i).revertMove();
222 }
223 lastE = e1;
224 if (print) {
225 logger.info(format(" Monte Carlo step rejected: e1 -> e2 %10.6f -> %10.6f", e1, e2));
226 }
227 return false;
228 }
229 }
230
231 /** {@inheritDoc} */
232 @Override
233 public void setPrint(boolean print) {
234 this.print = print;
235 }
236
237 /**
238 * Set the random seed.
239 *
240 * @param randomSeed The seed.
241 */
242 protected void setRandomSeed(int randomSeed) {
243 random.setSeed(randomSeed);
244 }
245
246 /**
247 * Must return the current energy of the system.
248 *
249 * @return Current system energy
250 */
251 protected abstract double currentEnergy();
252
253 /**
254 * Store the state for reverting a move. Must be properly implemented for revertStep() to function
255 * properly; otherwise, the implementation of revertStep() should throw an
256 * OperationNotSupportedException.
257 */
258 protected abstract void storeState();
259 }