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.algorithms.mc;
39  
40  import static ffx.utilities.Constants.R;
41  import static java.lang.String.format;
42  import static org.apache.commons.math3.util.FastMath.exp;
43  import static org.apache.commons.math3.util.FastMath.random;
44  
45  import ffx.algorithms.dynamics.thermostats.Thermostat;
46  import ffx.algorithms.optimize.Minimize;
47  import ffx.potential.ForceFieldEnergy;
48  import ffx.potential.MolecularAssembly;
49  import ffx.potential.bonded.Atom;
50  import ffx.potential.utils.Loop;
51  import java.util.List;
52  import java.util.Random;
53  import java.util.concurrent.ThreadLocalRandom;
54  import java.util.logging.Logger;
55  
56  /**
57   * MCLoop class.
58   *
59   * @author Armin Avdic
60   */
61  public class MCLoop implements MonteCarloListener {
62  
63    private static final Logger logger = Logger.getLogger(MCLoop.class.getName());
64    /** The MolecularAssembly. */
65    private final MolecularAssembly molecularAssembly;
66    /** The MD thermostat. */
67    private final Thermostat thermostat;
68    /**
69     * First residue number of loop.
70     */
71    private final int firstResidue;
72    /**
73     * Last residue number of loop.
74     */
75    private final int endResidue;
76    /** Everyone's favorite. */
77    private final Random random = new Random();
78    /** The ForceFieldEnergy object being used by MD. */
79    private final ForceFieldEnergy forceFieldEnergy;
80    /** KIC generation of loop solutions. See doi:10.1002/jcc.10416 */
81    final Loop loop;
82    /** The current MD step. */
83    private int stepCount = 0;
84    /** Number of simulation steps between MC move attempts. */
85    private final int mcStepFrequency;
86    /** Number of accepted MD moves. */
87    private int numMovesAccepted;
88    /** Number of KIC iterations per MC move. */
89    private int iterations;
90    /** List of active atoms. */
91    private Atom[] atoms;
92  
93    private boolean skipAlgorithm = false;
94  
95    /**
96     * Construct a Monte-Carlo loop switching mechanism.
97     *
98     * @param molecularAssembly the molecular assembly
99     * @param mcStepFrequency number of MD steps between switch attempts
100    * @param thermostat the MD thermostat
101    * @param firstResidue first residue number of loop
102    * @param endResidue last residue number of loop
103    */
104   MCLoop(MolecularAssembly molecularAssembly, int mcStepFrequency, Thermostat thermostat,
105       int firstResidue, int endResidue) {
106     numMovesAccepted = 0;
107 
108     this.molecularAssembly = molecularAssembly;
109     this.atoms = molecularAssembly.getAtomArray();
110     this.forceFieldEnergy = molecularAssembly.getPotentialEnergy();
111     this.mcStepFrequency = (mcStepFrequency == 0) ? Integer.MAX_VALUE : mcStepFrequency;
112     this.thermostat = thermostat;
113     /* Energy of the system at initialization. */
114     double systemReferenceEnergy = molecularAssembly.getPotentialEnergy().energy(false, true);
115     this.firstResidue = firstResidue;
116     this.endResidue = endResidue;
117     this.iterations = 1;
118 
119     if ((endResidue - firstResidue) < 3) {
120       logger.info("MCLoop requires at least 3 residues. First and last residues are anchors.");
121       skipAlgorithm = true;
122     }
123     String sb =
124         " Running MCLoop:\n" + format("     mcStepFrequency: %4d\n", mcStepFrequency) + format(
125             "     referenceEnergy: %7.2f\n", systemReferenceEnergy);
126     logger.info(sb);
127 
128     loop = new Loop(molecularAssembly);
129   }
130 
131   /**
132    * Get the current MC acceptance rate.
133    *
134    * @return the acceptance rate.
135    */
136   public double getAcceptanceRate() {
137     // Intentional integer division.
138     int numTries = stepCount / mcStepFrequency;
139     return (double) numMovesAccepted / numTries;
140   }
141 
142   /**
143    * {@inheritDoc}
144    *
145    * <p>The primary driver. Called by the MD engine at each dynamics step.
146    */
147   @Override
148   public boolean mcUpdate(double temperature) {
149 
150     stepCount++;
151     if (skipAlgorithm) {
152       return false;
153     }
154     // Decide on the type of step to be taken.
155     if ((stepCount % mcStepFrequency != 0)) {
156       // Not yet time for an MC step, return to MD.
157       return false;
158     }
159 
160     atoms = molecularAssembly.getAtomArray();
161 
162     // Randomly choose a target sub portion of loop to KIC.
163     int midResidue;
164     midResidue = ThreadLocalRandom.current().nextInt(firstResidue + 1, endResidue);
165 
166     List<double[]> loopSolutions;
167     loopSolutions = loop.generateLoops(midResidue - 1, midResidue + 1);
168 
169     for (int i = 1; i < iterations; i++) {
170       // pick random subLoop
171       midResidue = ThreadLocalRandom.current().nextInt(firstResidue + 1, endResidue);
172       // pick random solution
173       if (!loopSolutions.isEmpty()) {
174         List<double[]> tempLoops = loop.generateLoops(midResidue - 1, midResidue + 1,
175             loopSolutions.get(random.nextInt(loopSolutions.size())));
176         loopSolutions.addAll(tempLoops);
177       } else {
178         loopSolutions = loop.generateLoops(midResidue - 1, midResidue + 1);
179       }
180     }
181     int numLoopsFound = loopSolutions.size();
182     // Check whether KIC found alternative loops
183     if (numLoopsFound <= 1) {
184       return false;
185     }
186 
187     // Perform the MC move.
188     return tryLoopStep(loopSolutions);
189   }
190 
191   /**
192    * Setter for the field <code>iterations</code>.
193    *
194    * @param iterations The number of KIC iterations per MC move.
195    */
196   public void setIterations(int iterations) {
197     this.iterations = iterations;
198   }
199 
200   /**
201    * Perform a loop MC move.
202    *
203    * @param loopSolutions A list of loop solutions.
204    * @return accept/reject
205    */
206   private boolean tryLoopStep(List<double[]> loopSolutions) {
207 
208     // Choose from the list of available loops and save current coordinates
209     double[] newCoords = loopSolutions.get(random.nextInt(loopSolutions.size()));
210     // Storage for coordinates before MC move.
211     double[] oldCoords = storeActiveCoordinates();
212 
213     // Optimize the system.
214     Minimize minimize1 = new Minimize(null, forceFieldEnergy, null);
215     minimize1.minimize();
216     double originalLoopEnergy = forceFieldEnergy.energy(false, true);
217 
218     // Perform move and analysis of chosen loop.
219     performLoopMove(newCoords);
220     Minimize minimize2 = new Minimize(null, forceFieldEnergy, null);
221     minimize2.minimize();
222     double newLoopEnergy = forceFieldEnergy.energy(false, true);
223 
224     double temperature = thermostat.getCurrentTemperature();
225     double kT = R * temperature;
226     // Test the MC criterion for a loop move.
227     double dE = Math.abs(originalLoopEnergy - newLoopEnergy);
228     if (newLoopEnergy < originalLoopEnergy) {
229       dE = -dE;
230     }
231 
232     StringBuilder sb = new StringBuilder();
233     sb.append(" Assessing possible MC loop move step:\n");
234     sb.append(format("     original loop: %16.8f\n", originalLoopEnergy));
235     sb.append(format("     possible loop: %16.8f\n", newLoopEnergy));
236     sb.append("     -----\n");
237 
238     // Test Monte-Carlo criterion.
239     if (dE < 0) {
240       sb.append("     Accepted!");
241       logger.info(sb.toString());
242       numMovesAccepted++;
243       return true;
244     }
245     double criterion = exp(-dE / kT);
246 
247     double metropolis = random();
248     sb.append(format("     criterion:  %9.4f\n", criterion));
249     sb.append(format("     rng:        %9.4f\n", metropolis));
250     if ((metropolis < criterion)) {
251       sb.append("     Accepted!");
252       logger.info(sb.toString());
253       numMovesAccepted++;
254       return true;
255     }
256     sb.append("     Denied.");
257     logger.info(sb.toString());
258 
259     // Move was rejected, undo the loop move
260     performLoopMove(oldCoords);
261     return false;
262   }
263 
264   /**
265    * Perform the requested coordinate move
266    *
267    * @param newCoordinates THe new coordinates.
268    */
269   private void performLoopMove(double[] newCoordinates) {
270     int index = 0;
271     for (Atom a : atoms) {
272       double x = newCoordinates[index++];
273       double y = newCoordinates[index++];
274       double z = newCoordinates[index++];
275       a.moveTo(x, y, z);
276     }
277   }
278 
279   private double[] storeActiveCoordinates() {
280     double[] coords = new double[atoms.length * 3];
281     int index = 0;
282     for (Atom a : atoms) {
283       coords[index++] = a.getX();
284       coords[index++] = a.getY();
285       coords[index++] = a.getZ();
286     }
287     return coords;
288   }
289 
290 }