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