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.optimize.manybody;
39  
40  import static java.lang.String.format;
41  
42  import edu.rit.mp.DoubleBuf;
43  import edu.rit.pj.Comm;
44  import edu.rit.pj.IntegerSchedule;
45  import edu.rit.pj.WorkerIntegerForLoop;
46  import edu.rit.pj.WorkerRegion;
47  import ffx.algorithms.optimize.RotamerOptimization;
48  import ffx.potential.bonded.Residue;
49  import ffx.potential.bonded.Rotamer;
50  import ffx.potential.utils.EnergyException;
51  import java.io.BufferedWriter;
52  import java.io.IOException;
53  import java.util.List;
54  import java.util.Map;
55  import java.util.Set;
56  import java.util.logging.Level;
57  import java.util.logging.Logger;
58  
59  /** Compute 2-Body energy values in parallel across nodes. */
60  public class TwoBodyEnergyRegion extends WorkerRegion {
61  
62    private static final Logger logger = Logger.getLogger(TwoBodyEnergyRegion.class.getName());
63    private final Residue[] residues;
64    private final RotamerOptimization rO;
65    private final DistanceMatrix dM;
66    private final EnergyExpansion eE;
67    private final EliminatedRotamers eR;
68    /**
69     * A list of all residues being optimized. Note that Box and Window optimizations operate on
70     * subsets of this list.
71     */
72    private final List<Residue> allResiduesList;
73    /** Map of self-energy values to compute. */
74    private final Map<Integer, Integer[]> twoBodyEnergyMap;
75    /** Writes energies to restart file. */
76    private final BufferedWriter energyWriter;
77    /** World Parallel Java communicator. */
78    private final Comm world;
79    /** Number of Parallel Java processes. */
80    private final int numProc;
81    /** Flag to prune clashes. */
82    private final boolean prunePairClashes;
83    /**
84     * If a pair of residues have two atoms closer together than the superposition threshold, the
85     * energy is set to NaN.
86     */
87    private final double superpositionThreshold;
88    /** Flag to indicate if this is the master process. */
89    private final boolean master;
90    /** Rank of this process. */
91    private final int rank;
92    /** Flag to indicate verbose logging. */
93    private final boolean verbose;
94    /** If true, write out an energy restart file. */
95    private final boolean writeEnergyRestart;
96    /**
97     * Sets whether files should be printed; true for standalone applications, false for some
98     * applications which use rotamer optimization as part of a larger process.
99     */
100   private final boolean printFiles;
101 
102   private Set<Integer> keySet;
103 
104   public TwoBodyEnergyRegion(RotamerOptimization rotamerOptimization, DistanceMatrix dM,
105       EnergyExpansion eE, EliminatedRotamers eR, Residue[] residues, List<Residue> allResiduesList,
106       BufferedWriter energyWriter, Comm world, int numProc, boolean prunePairClashes,
107       double superpositionThreshold, boolean master, int rank, boolean verbose,
108       boolean writeEnergyRestart, boolean printFiles) {
109     this.rO = rotamerOptimization;
110     this.dM = dM;
111     this.eE = eE;
112     this.eR = eR;
113     this.residues = residues;
114     this.allResiduesList = allResiduesList;
115     this.energyWriter = energyWriter;
116     this.world = world;
117     this.numProc = numProc;
118     this.prunePairClashes = prunePairClashes;
119     this.superpositionThreshold = superpositionThreshold;
120     this.master = master;
121     this.rank = rank;
122     this.verbose = verbose;
123     this.writeEnergyRestart = writeEnergyRestart;
124     this.printFiles = printFiles;
125 
126     this.twoBodyEnergyMap = eE.getTwoBodyEnergyMap();
127     logger.info(format(" Number of 2-body energies to calculate: %d", twoBodyEnergyMap.size()));
128   }
129 
130   @Override
131   public void finish() {
132     // Pre-Prune if pair-energy is Double.NaN.
133     eR.prePrunePairs(residues);
134 
135     // Prune each rotamer that clashes with all rotamers from a 2nd residue.
136     if (prunePairClashes) {
137       eR.prunePairClashes(residues);
138     }
139 
140     // Print what we've got so far.
141     if (master && verbose) {
142       for (int i = 0; i < residues.length; i++) {
143         Residue resI = residues[i];
144         Rotamer[] rotI = resI.getRotamers();
145         for (int ri = 0; ri < rotI.length; ri++) {
146           if (eR.check(i, ri)) {
147             continue;
148           }
149           for (int j = i + 1; j < residues.length; j++) {
150             Residue resJ = residues[j];
151             Rotamer[] rotJ = resJ.getRotamers();
152             for (int rj = 0; rj < rotJ.length; rj++) {
153               if (eR.check(j, rj) || eR.check(i, ri, j, rj)) {
154                 continue;
155               }
156               logger.info(
157                   format(" Pair energy %8s %-2d, %8s %-2d: %s", residues[i].toString(rotI[ri]), ri,
158                       residues[j].toString(rotJ[rj]), rj,
159                       rO.formatEnergy(eE.get2Body(i, ri, j, rj))));
160             }
161           }
162         }
163       }
164     }
165   }
166 
167   @Override
168   public void run() throws Exception {
169     if (!keySet.isEmpty()) {
170       execute(0, keySet.size() - 1, new TwoBodyEnergyLoop());
171     }
172   }
173 
174   @Override
175   public void start() {
176     int numPair = twoBodyEnergyMap.size();
177     int remainder = numPair % numProc;
178 
179     // Set padded residue and rotamer to less than zero.
180     Integer[] padding = {-1, -1, -1, -1};
181 
182     int padKey = numPair;
183     while (remainder != 0) {
184       twoBodyEnergyMap.put(padKey++, padding);
185       remainder = twoBodyEnergyMap.size() % numProc;
186     }
187 
188     numPair = twoBodyEnergyMap.size();
189     if (numPair % numProc != 0) {
190       logger.severe(" Logic error padding pair energies.");
191     }
192 
193     // Load the keySet of pair energies.
194     keySet = twoBodyEnergyMap.keySet();
195   }
196 
197   private class TwoBodyEnergyLoop extends WorkerIntegerForLoop {
198 
199     final DoubleBuf[] resultBuffer;
200     final DoubleBuf myBuffer;
201 
202     TwoBodyEnergyLoop() {
203       resultBuffer = new DoubleBuf[numProc];
204       for (int i = 0; i < numProc; i++) {
205         resultBuffer[i] = DoubleBuf.buffer(new double[5]);
206       }
207       myBuffer = resultBuffer[rank];
208     }
209 
210     @Override
211     public void run(int lb, int ub) {
212       for (int key = lb; key <= ub; key++) {
213         long time = -System.nanoTime();
214         Integer[] job = twoBodyEnergyMap.get(key);
215         int i = job[0];
216         int ri = job[1];
217         int j = job[2];
218         int rj = job[3];
219 
220         myBuffer.put(0, i);
221         myBuffer.put(1, ri);
222         myBuffer.put(2, j);
223         myBuffer.put(3, rj);
224         myBuffer.put(4, 0.0);
225 
226         // Initialize result.
227         if (i >= 0 && ri >= 0 && j >= 0 && rj >= 0) {
228           if (!eR.check(i, ri) || !eR.check(j, rj) || !eR.check(i, ri, j, rj)) {
229             Residue residueI = residues[i];
230             Residue residueJ = residues[j];
231             Rotamer[] rotI = residues[i].getRotamers();
232             Rotamer[] rotJ = residues[j].getRotamers();
233             int indexI = allResiduesList.indexOf(residueI);
234             int indexJ = allResiduesList.indexOf(residueJ);
235             double resDist = dM.getResidueDistance(indexI, ri, indexJ, rj);
236             String resDistString = "large";
237             if (resDist < Double.MAX_VALUE) {
238               resDistString = format("%5.3f", resDist);
239             }
240 
241             double dist = dM.checkDistMatrix(indexI, ri, indexJ, rj);
242             String distString = "     large";
243             if (dist < Double.MAX_VALUE) {
244               distString = format("%10.3f", dist);
245             }
246 
247             double twoBodyEnergy;
248             if (dist < superpositionThreshold) {
249               // Set the energy to NaN for superposed atoms.
250               twoBodyEnergy = Double.NaN;
251               logger.info(
252                   format(" Pair %8s %-2d, %8s %-2d:\t    NaN at %10.3f A (%s A by res) < %5.3f Ang",
253                       residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj, dist,
254                       resDist, superpositionThreshold));
255             } else if (dM.checkPairDistThreshold(indexI, ri, indexJ, rj)) {
256               // Set the two-body energy to 0.0 for separation distances larger than the two-body cutoff.
257               twoBodyEnergy = 0.0;
258               time += System.nanoTime();
259               logger.info(
260                   format(" Pair %8s %-2d, %8s %-2d: %s at %s A (%s A by res) in %6.4f (sec).",
261                       residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj,
262                       rO.formatEnergy(twoBodyEnergy), distString, resDistString, time * 1.0e-9));
263             } else {
264               try {
265                 twoBodyEnergy = eE.compute2BodyEnergy(residues, i, ri, j, rj);
266                 time += System.nanoTime();
267                 logger.info(
268                     format(" Pair %8s %-2d, %8s %-2d: %s at %s A (%s A by res) in %6.4f (sec).",
269                         residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj,
270                         rO.formatEnergy(twoBodyEnergy), distString, resDistString, time * 1.0e-9));
271               } catch (EnergyException ex) {
272                 twoBodyEnergy = ex.getEnergy();
273                 time += System.nanoTime();
274                 logger.info(
275                     format(" Pair %8s %-2d, %8s %-2d: %s at %s A (%s A by res) in %6.4f (sec).",
276                         residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj,
277                         rO.formatEnergy(twoBodyEnergy), distString, resDistString, time * 1.0e-9));
278               }
279             }
280             myBuffer.put(4, twoBodyEnergy);
281           }
282         }
283 
284         // All to All communication
285         if (numProc > 1) {
286           try {
287             world.allGather(myBuffer, resultBuffer);
288           } catch (Exception e) {
289             logger.log(Level.SEVERE, " Exception communicating pair energies.", e);
290           }
291         }
292 
293         // Process the two-body energy received from each process.
294         for (DoubleBuf doubleBuf : resultBuffer) {
295           int resI = (int) doubleBuf.get(0);
296           int rotI = (int) doubleBuf.get(1);
297           int resJ = (int) doubleBuf.get(2);
298           int rotJ = (int) doubleBuf.get(3);
299           double energy = doubleBuf.get(4);
300           // Skip for padded result.
301           if (resI >= 0 && rotI >= 0 && resJ >= 0 && rotJ >= 0) {
302             if (!Double.isFinite(energy)) {
303               logger.info(
304                   " Rotamer pair eliminated: " + resI + ", " + rotI + ", " + resJ + ", " + rotJ);
305               eR.eliminateRotamerPair(residues, resI, rotI, resJ, rotJ, false);
306             }
307             eE.set2Body(resI, rotI, resJ, rotJ, energy);
308             if (rank == 0 && writeEnergyRestart && printFiles) {
309               try {
310                 energyWriter.append(
311                     format("Pair %d %d, %d %d: %16.8f", resI, rotI, resJ, rotJ, energy));
312                 energyWriter.newLine();
313                 energyWriter.flush();
314               } catch (IOException ex) {
315                 logger.log(Level.SEVERE, " Exception writing energy restart file.", ex);
316               }
317             }
318           }
319         }
320       }
321     }
322 
323     @Override
324     public IntegerSchedule schedule() {
325       // The schedule must be fixed.
326       return IntegerSchedule.fixed();
327     }
328   }
329 }