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.optimize.manybody;
39  
40  import ffx.algorithms.AlgorithmListener;
41  import ffx.algorithms.optimize.RotamerOptimization;
42  import ffx.numerics.Potential;
43  import ffx.potential.MolecularAssembly;
44  import ffx.potential.bonded.Atom;
45  import ffx.potential.bonded.Residue;
46  import ffx.potential.bonded.Rotamer;
47  import ffx.potential.openmm.OpenMMEnergy;
48  import ffx.potential.utils.EnergyException;
49  import org.apache.commons.configuration2.CompositeConfiguration;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.nio.charset.StandardCharsets;
54  import java.nio.file.Files;
55  import java.nio.file.Path;
56  import java.nio.file.Paths;
57  import java.util.ArrayList;
58  import java.util.HashMap;
59  import java.util.List;
60  import java.util.Map;
61  import java.util.Set;
62  import java.util.logging.Level;
63  import java.util.logging.Logger;
64  
65  import static ffx.potential.bonded.RotamerLibrary.applyRotamer;
66  import static java.lang.String.format;
67  
68  public class EnergyExpansion {
69  
70      private static final Logger logger = Logger.getLogger(EnergyExpansion.class.getName());
71      /**
72       * Default value for the ommRecalculateThreshold in kcal/mol.
73       */
74      private static final double DEFAULT_OMM_RECALCULATE_THRESHOLD = -200;
75      /**
76       * Default value for the singularityThreshold in kcal/mol.
77       */
78      private static final double DEFAULT_SINGULARITY_THRESHOLD = -1000;
79      /**
80       * Map of self-energy values to compute.
81       */
82      private final Map<Integer, Integer[]> selfEnergyMap = new HashMap<>();
83      /**
84       * Map of 2-body energy values to compute.
85       */
86      private final Map<Integer, Integer[]> twoBodyEnergyMap = new HashMap<>();
87      /**
88       * Map of 3-body energy values to compute.
89       */
90      private final Map<Integer, Integer[]> threeBodyEnergyMap = new HashMap<>();
91      /**
92       * Map of 4-body energy values to compute.
93       */
94      private final Map<Integer, Integer[]> fourBodyEnergyMap = new HashMap<>();
95      /**
96       * Flag to indicate if this is the master process.
97       */
98      private final boolean master;
99      /**
100      * If OpenMM is in use, recalculate any suspiciously low self/pair/triple energies using the
101      * reference Java implementation.
102      */
103     private final double ommRecalculateThreshold;
104     /**
105      * Reject any self, pair, or triple energy beneath singularityThreshold.
106      *
107      * <p>This is meant to check for running into singularities in the energy function, where an
108      * energy is "too good to be true" and actually represents a poor conformation.
109      */
110     private final double singularityThreshold;
111     /**
112      * Indicates if the Potential is an OpenMMForceFieldEnergy.
113      */
114     private final boolean potentialIsOpenMM;
115     private final RotamerOptimization rO;
116     private final DistanceMatrix dM;
117     private final EliminatedRotamers eR;
118     /**
119      * MolecularAssembly to perform rotamer optimization on.
120      */
121     private final MolecularAssembly molecularAssembly;
122     /**
123      * AlgorithmListener who should receive updates as the optimization runs.
124      */
125     private final AlgorithmListener algorithmListener;
126     /**
127      * A list of all residues being optimized. Note that Box and Window optimizations operate on
128      * subsets of this list.
129      */
130     private final List<Residue> allResiduesList;
131     /**
132      * Interaction partners of a Residue that come after it.
133      */
134     private final int[][] resNeighbors;
135     /**
136      * Flag to control use of 3-body terms.
137      */
138     private final boolean threeBodyTerm;
139     /**
140      * Flag to indicate computation of a many-body expansion for original coordinates.
141      */
142     private final boolean decomposeOriginal;
143     /**
144      * Flag to indicate use of box optimization.
145      */
146     private final boolean usingBoxOptimization;
147     /**
148      * Flag to indicate verbose logging.
149      */
150     private final boolean verbose;
151     /**
152      * Flag to prune clashes.
153      */
154     private final boolean pruneClashes;
155     /**
156      * Flag to prune pair clashes.
157      */
158     private final boolean prunePairClashes;
159     /**
160      * Maximum number of 4-body energy values to compute.
161      */
162     private final int max4BodyCount;
163     /**
164      * The potential energy of the system with all side-chains to be optimized turned off.
165      */
166     private double backboneEnergy;
167     /**
168      * Self-energy of each residue for each rotamer. [residue][rotamer]
169      */
170     private double[][] selfEnergy;
171     /**
172      * Two-body energies for each pair of residues and each pair of rotamers.
173      * [residue1][rotamer1][residue2][rotamer2]
174      */
175     private double[][][][] twoBodyEnergy;
176     /**
177      * Trimer-energies for each trimer of rotamers.
178      * [residue1][rotamer1][residue2][rotamer2][residue3][rotamer3]
179      */
180     private double[][][][][][] threeBodyEnergy;
181 
182     public EnergyExpansion(RotamerOptimization rO, DistanceMatrix dM, EliminatedRotamers eR,
183                            MolecularAssembly molecularAssembly, Potential potential, AlgorithmListener algorithmListener,
184                            List<Residue> allResiduesList, int[][] resNeighbors, boolean threeBodyTerm,
185                            boolean decomposeOriginal, boolean usingBoxOptimization, boolean verbose,
186                            boolean pruneClashes, boolean prunePairClashes, boolean master) {
187         this.rO = rO;
188         this.dM = dM;
189         this.eR = eR;
190         this.molecularAssembly = molecularAssembly;
191         this.algorithmListener = algorithmListener;
192         this.allResiduesList = allResiduesList;
193         this.resNeighbors = resNeighbors;
194         this.threeBodyTerm = threeBodyTerm;
195         this.decomposeOriginal = decomposeOriginal;
196         this.usingBoxOptimization = usingBoxOptimization;
197         this.verbose = verbose;
198         this.pruneClashes = pruneClashes;
199         this.prunePairClashes = prunePairClashes;
200         this.master = master;
201 
202         CompositeConfiguration properties = molecularAssembly.getProperties();
203         max4BodyCount = properties.getInt("ro-max4BodyCount", Integer.MAX_VALUE);
204         if (max4BodyCount != Integer.MAX_VALUE) {
205             logger.info(format(" Max 4Body Count: %d", max4BodyCount));
206         }
207         singularityThreshold = properties.getDouble("ro-singularityThreshold", DEFAULT_SINGULARITY_THRESHOLD);
208         potentialIsOpenMM = potential instanceof OpenMMEnergy;
209         if (potentialIsOpenMM) {
210             ommRecalculateThreshold = properties.getDouble("ro-ommRecalculateThreshold", DEFAULT_OMM_RECALCULATE_THRESHOLD);
211         } else {
212             ommRecalculateThreshold = -1E200;
213         }
214     }
215 
216     /**
217      * Set the "use" flag to true for all variable atoms in a residue.
218      *
219      * @param residue The residue to turn off.
220      */
221     private static void turnOffAtoms(Residue residue) {
222         switch (residue.getResidueType()) {
223             case NA:
224             case AA:
225                 List<Atom> atomList = residue.getVariableAtoms();
226                 for (Atom atom : atomList) {
227                     atom.setUse(false);
228                 }
229                 break;
230             default:
231                 atomList = residue.getAtomList();
232                 for (Atom atom : atomList) {
233                     atom.setUse(false);
234                 }
235                 break;
236         }
237     }
238 
239     /**
240      * Set the "use" flag to true for all variable atoms in a residue.
241      *
242      * @param residue The Residue to turn on.
243      */
244     private static void turnOnAtoms(Residue residue) {
245         switch (residue.getResidueType()) {
246             case NA:
247             case AA:
248                 List<Atom> atomList = residue.getVariableAtoms();
249                 for (Atom atom : atomList) {
250                     atom.setUse(true);
251                 }
252                 break;
253             default:
254                 atomList = residue.getAtomList();
255                 for (Atom atom : atomList) {
256                     atom.setUse(true);
257                 }
258                 break;
259         }
260     }
261 
262     public HashMap<String, Integer> allocate2BodyJobMap(Residue[] residues, int nResidues,
263                                                         boolean reverseMap) {
264         twoBodyEnergyMap.clear();
265         // allocated twoBodyEnergy array and create pair jobs
266         HashMap<String, Integer> reverseJobMapPairs = new HashMap<>();
267         int pairJobIndex = 0;
268         twoBodyEnergy = new double[nResidues][][][];
269         for (int i = 0; i < nResidues; i++) {
270             Residue resi = residues[i];
271             int indexI = allResiduesList.indexOf(resi);
272             Rotamer[] roti = resi.getRotamers();
273             int[] nI = resNeighbors[i];
274             int lenNI = nI.length;
275             twoBodyEnergy[i] = new double[roti.length][lenNI][];
276 
277             for (int ri = 0; ri < roti.length; ri++) {
278                 if (eR.check(i, ri)) {
279                     continue;
280                 }
281                 // for (int j = i + 1; j < nResidues; j++) {
282                 for (int indJ = 0; indJ < lenNI; indJ++) {
283                     int j = nI[indJ];
284                     if (rO.checkNeighboringPair(i, j)) {
285                         Residue resj = residues[j];
286                         int indexJ = allResiduesList.indexOf(resj);
287                         Rotamer[] rotj = resj.getRotamers();
288                         twoBodyEnergy[i][ri][indJ] = new double[rotj.length];
289                         for (int rj = 0; rj < rotj.length; rj++) {
290                             if (eR.checkToJ(i, ri, j, rj)) {
291                                 continue;
292                             }
293 
294                             // Skip creating a job if the pair is outside pair cut-off.
295                             if (dM.checkPairDistThreshold(indexI, ri, indexJ, rj)) {
296                                 continue;
297                             }
298 
299                             Integer[] pairJob = {i, ri, j, rj};
300                             if (decomposeOriginal && (ri != 0 || rj != 0)) {
301                                 continue;
302                             }
303                             twoBodyEnergyMap.put(pairJobIndex, pairJob);
304                             if (reverseMap) {
305                                 String revKey = format("%d %d %d %d", i, ri, j, rj);
306                                 reverseJobMapPairs.put(revKey, pairJobIndex);
307                             }
308                             pairJobIndex++;
309                         }
310                     }
311                 }
312             }
313         }
314         return reverseJobMapPairs;
315     }
316 
317     public HashMap<String, Integer> allocate3BodyJobMap(Residue[] residues, int nResidues,
318                                                         boolean reverseMap) {
319         HashMap<String, Integer> reverseJobMapTrimers = new HashMap<>();
320         threeBodyEnergyMap.clear();
321         // fill in 3-Body energies from the restart file.
322         threeBodyEnergy = new double[nResidues][][][][][];
323         int trimerJobIndex = 0;
324         for (int i = 0; i < nResidues; i++) {
325             Residue resi = residues[i];
326             int indexI = allResiduesList.indexOf(resi);
327             Rotamer[] roti = resi.getRotamers();
328             int lenri = roti.length;
329             int[] nI = resNeighbors[i];
330             int lenNI = nI.length;
331             threeBodyEnergy[i] = new double[lenri][lenNI][][][];
332 
333             for (int ri = 0; ri < lenri; ri++) {
334                 if (eR.check(i, ri)) {
335                     continue;
336                 }
337                 for (int indJ = 0; indJ < lenNI; indJ++) {
338                     // for (int j = i + 1; j < nResidues; j++) {
339                     int j = nI[indJ];
340                     Residue resj = residues[j];
341                     int indexJ = allResiduesList.indexOf(resj);
342                     Rotamer[] rotj = resj.getRotamers();
343                     int lenrj = rotj.length;
344                     int[] nJ = resNeighbors[j];
345                     int lenNJ = nJ.length;
346                     threeBodyEnergy[i][ri][indJ] = new double[lenrj][lenNJ][];
347 
348                     for (int rj = 0; rj < lenrj; rj++) {
349                         if (eR.checkToJ(i, ri, j, rj)) {
350                             continue;
351                         }
352                         // for (int k = j + 1; k < nResidues; k++) {
353                         for (int indK = 0; indK < lenNJ; indK++) {
354                             int k = nJ[indK];
355                             Residue resk = residues[k];
356                             int indexK = allResiduesList.indexOf(resk);
357                             Rotamer[] rotk = resk.getRotamers();
358                             int lenrk = rotk.length;
359                             threeBodyEnergy[i][ri][indJ][rj][indK] = new double[lenrk];
360 
361                             for (int rk = 0; rk < lenrk; rk++) {
362                                 if (eR.checkToK(i, ri, j, rj, k, rk)) {
363                                     continue;
364                                 }
365                                 if (dM.checkTriDistThreshold(indexI, ri, indexJ, rj, indexK, rk)) {
366                                     continue;
367                                 }
368                                 Integer[] trimerJob = {i, ri, j, rj, k, rk};
369                                 if (decomposeOriginal && (ri != 0 || rj != 0 || rk != 0)) {
370                                     continue;
371                                 }
372                                 threeBodyEnergyMap.put(trimerJobIndex, trimerJob);
373                                 if (reverseMap) {
374                                     String revKey = format("%d %d %d %d %d %d", i, ri, j, rj, k, rk);
375                                     reverseJobMapTrimers.put(revKey, trimerJobIndex);
376                                 }
377                                 trimerJobIndex++;
378                             }
379                         }
380                     }
381                 }
382             }
383         }
384         return reverseJobMapTrimers;
385     }
386 
387     public void allocate4BodyJobMap(Residue[] residues, int nResidues) {
388         logger.info(" Creating 4-Body jobs...");
389         fourBodyEnergyMap.clear();
390         boolean maxedOut = false;
391         // create 4-Body jobs (no memory allocation)
392         int quadJobIndex = 0;
393         for (int i = 0; i < nResidues; i++) {
394             Residue resi = residues[i];
395             Rotamer[] roti = resi.getRotamers();
396             for (int ri = 0; ri < roti.length; ri++) {
397                 if (eR.check(i, ri)) {
398                     continue;
399                 }
400                 for (int j = i + 1; j < nResidues; j++) {
401                     Residue resj = residues[j];
402                     Rotamer[] rotj = resj.getRotamers();
403                     for (int rj = 0; rj < rotj.length; rj++) {
404             /*if (check(j, rj) || check(i, ri, j, rj)) {
405             continue;
406             }*/
407                         if (eR.checkToJ(i, ri, j, rj)) {
408                             continue;
409                         }
410                         for (int k = j + 1; k < nResidues; k++) {
411                             Residue resk = residues[k];
412                             Rotamer[] rotk = resk.getRotamers();
413                             for (int rk = 0; rk < rotk.length; rk++) {
414                 /*if (check(k, rk) || check(i, ri, k, rk) || check(j, rj, k, rk) || check(i, ri, j, rj, k, rk)) {
415                 continue;
416                 }*/
417                                 if (eR.checkToK(i, ri, j, rj, k, rk)) {
418                                     continue;
419                                 }
420                                 for (int l = k + 1; l < nResidues; l++) {
421                                     Residue resl = residues[l];
422                                     Rotamer[] rotl = resl.getRotamers();
423                                     for (int rl = 0; rl < rotl.length; rl++) {
424                                         if (eR.checkToL(i, ri, j, rj, k, rk, l, rl)) {
425                                             continue;
426                                         }
427                                         Integer[] quadJob = {i, ri, j, rj, k, rk, l, rl};
428                                         if (decomposeOriginal && (ri != 0 || rj != 0 || rk != 0 || rl != 0)) {
429                                             continue;
430                                         }
431                                         fourBodyEnergyMap.put(quadJobIndex++, quadJob);
432                                         if (fourBodyEnergyMap.size() > max4BodyCount) {
433                                             maxedOut = true;
434                                             break;
435                                         }
436                                     }
437                                     if (maxedOut) {
438                                         break;
439                                     }
440                                 }
441                                 if (maxedOut) {
442                                     break;
443                                 }
444                             }
445                             if (maxedOut) {
446                                 break;
447                             }
448                         }
449                         if (maxedOut) {
450                             break;
451                         }
452                     }
453                     if (maxedOut) {
454                         break;
455                     }
456                 }
457                 if (maxedOut) {
458                     break;
459                 }
460             }
461             if (maxedOut) {
462                 break;
463             }
464         }
465     }
466 
467     public HashMap<String, Integer> allocateSelfJobMap(Residue[] residues, int nResidues,
468                                                        boolean reverseMap) {
469         selfEnergyMap.clear();
470         // allocate selfEnergy array and create self jobs
471         HashMap<String, Integer> reverseJobMapSingles = new HashMap<>();
472         int singleJobIndex = 0;
473         selfEnergy = new double[nResidues][];
474         for (int i = 0; i < nResidues; i++) {
475             Residue resi = residues[i];
476             Rotamer[] roti = resi.getRotamers();
477             selfEnergy[i] = new double[roti.length];
478             for (int ri = 0; ri < roti.length; ri++) {
479                 if (!eR.check(i, ri)) {
480                     Integer[] selfJob = {i, ri};
481                     if (decomposeOriginal && ri != 0) {
482                         continue;
483                     }
484                     selfEnergyMap.put(singleJobIndex, selfJob);
485                     if (reverseMap) {
486                         String revKey = format("%d %d", i, ri);
487                         reverseJobMapSingles.put(revKey, singleJobIndex);
488                     }
489                     singleJobIndex++;
490                 }
491             }
492         }
493         return reverseJobMapSingles;
494     }
495 
496     /**
497      * Computes a pair energy, defined as energy with all side-chains but two turned off, minus the sum
498      * of backbone and component self energies.
499      *
500      * @param residues Residues under optimization.
501      * @param i        A residue index.
502      * @param ri       A rotamer index for residue i.
503      * @param j        A residue index j!=i.
504      * @param rj       A rotamer index for residue j.
505      * @return Epair(ri, rj)=E2(ri,rj)-Eself(ri)-Eself(rj)-Eenv/bb.
506      */
507     public double compute2BodyEnergy(Residue[] residues, int i, int ri, int j, int rj) {
508         rO.turnOffAllResidues(residues);
509         turnOnResidue(residues[i], ri);
510         turnOnResidue(residues[j], rj);
511         double energy;
512         try {
513             if (algorithmListener != null) {
514                 algorithmListener.algorithmUpdate(molecularAssembly);
515             }
516             Rotamer[] rot_i = residues[i].getRotamers();
517             Rotamer[] rot_j = residues[j].getRotamers();
518             double subtract =
519                     -backboneEnergy - getSelf(i, ri, rot_i[ri], true) - getSelf(j, rj, rot_j[rj], true);
520 
521             //double subtract = -backboneEnergy - getSelf(i, ri) - getSelf(j, rj);
522             energy = rO.currentEnergy(residues) + subtract;
523             if (logger.isLoggable(Level.FINE)) {
524                 logger.fine(format(" %s Pair-Energy %16.8f = FF %16.8f - BB %16.8f + Self Ri %16.8f + Self Rj %16.8f ",
525                         rot_i[ri].getName() + "-" + rot_j[rj].getName(), energy, energy + backboneEnergy, backboneEnergy,
526                         getSelf(i, ri, rot_i[ri], true) , getSelf(j, rj, rot_j[rj], true)));
527             }
528             if (potentialIsOpenMM && energy < ommRecalculateThreshold) {
529                 logger.warning(
530                         format(" Experimental: re-computing pair energy %s-%d %s-%d using Force Field X",
531                                 residues[i], ri, residues[j], rj));
532                 energy = rO.currentFFXPE() + subtract;
533             }
534             if (energy < singularityThreshold) {
535                 String message = format(
536                         " Rejecting pair energy for %s-%d %s-%d is %10.5g << %10f, " + "likely in error.",
537                         residues[i], ri, residues[j], rj, energy, singularityThreshold);
538                 logger.warning(message);
539                 throw new EnergyException(message, false, energy);
540             }
541         } finally {
542             // Revert if the currentEnergy call throws an exception.
543             turnOffResidue(residues[i]);
544             turnOffResidue(residues[j]);
545         }
546         return energy;
547     }
548 
549     /**
550      * Computes a 3-body energy, defined as the energy with all sidechains but three turned off, minus
551      * the sum of backbone and component self/2-Body energies.
552      *
553      * @param residues Residues under optimization.
554      * @param i        A residue index.
555      * @param ri       A rotamer index for residue i.
556      * @param j        A residue index j!=i.
557      * @param rj       A rotamer index for residue j.
558      * @param k        A residue index k!=j k!=i.
559      * @param rk       A rotamer index for residue k.
560      * @return Etri(ri,
561      *rj)=E3(ri,rj,rk)-Epair(ri,rj)-Epair(ri,rk)-Epair(rj,rk)-Eself(ri)-Eself(rj)-Eself(rk)-Eenv/bb.
562      */
563     public double compute3BodyEnergy(Residue[] residues, int i, int ri, int j, int rj, int k, int rk) {
564         turnOffAllResidues(residues);
565         turnOnResidue(residues[i], ri);
566         turnOnResidue(residues[j], rj);
567         turnOnResidue(residues[k], rk);
568         Rotamer[] rot_i = residues[i].getRotamers();
569         Rotamer[] rot_j = residues[j].getRotamers();
570         Rotamer[] rot_k = residues[k].getRotamers();
571         double energy;
572         try {
573             if (algorithmListener != null) {
574                 algorithmListener.algorithmUpdate(molecularAssembly);
575             }
576             double subtract =
577                     -backboneEnergy - getSelf(i, ri, rot_i[ri], true) - getSelf(j, rj, rot_j[rj], true)
578                             - getSelf(k, rk, rot_k[rk], true) - get2Body(i, ri, j, rj) - get2Body(i, ri, k, rk)
579                             - get2Body(j, rj, k, rk);
580             energy = rO.currentEnergy(residues) + subtract;
581             if (potentialIsOpenMM && energy < ommRecalculateThreshold) {
582                 logger.warning(
583                         format(" Experimental: re-computing triple energy %s-%d %s-%d %s-%d using Force Field X",
584                                 residues[i], ri, residues[j], rj, residues[k], rk));
585                 energy = rO.currentFFXPE() + subtract;
586             }
587             if (energy < singularityThreshold) {
588                 String message = format(" Rejecting triple energy for %s-%d %s-%d %s-%d is %10.5g << %10f, "
589                                 + "likely in error.", residues[i], ri, residues[j], rj, residues[k], rk, energy,
590                         singularityThreshold);
591                 logger.warning(message);
592                 throw new EnergyException(message);
593             }
594         } finally {
595             // Revert if the currentEnergy call throws an exception.
596             turnOffResidue(residues[i]);
597             turnOffResidue(residues[j]);
598             turnOffResidue(residues[k]);
599         }
600         return energy;
601     }
602 
603     /**
604      * Computes a 4-body energy, defined as the energy with all sidechains but four turned off, minus
605      * the sum of backbone and component self/2-Body/3-body energies.
606      *
607      * @param residues Residues under optimization.
608      * @param i        A residue index.
609      * @param ri       A rotamer index for residue i.
610      * @param j        A residue index j!=i.
611      * @param rj       A rotamer index for residue j.
612      * @param k        A residue index k!=j k!=i.
613      * @param rk       A rotamer index for residue k.
614      * @param l        A residue index l!=i l!=j l!=k.
615      * @param rl       A rotamer index for residue l.
616      * @return The 4-body energy.
617      */
618     public double compute4BodyEnergy(Residue[] residues, int i, int ri, int j, int rj, int k, int rk,
619                                      int l, int rl) {
620         turnOffAllResidues(residues);
621         turnOnResidue(residues[i], ri);
622         turnOnResidue(residues[j], rj);
623         turnOnResidue(residues[k], rk);
624         turnOnResidue(residues[l], rl);
625         double energy;
626         try {
627             if (algorithmListener != null) {
628                 algorithmListener.algorithmUpdate(molecularAssembly);
629             }
630             double subtract =
631                     -backboneEnergy - getSelf(i, ri) - getSelf(j, rj) - getSelf(k, rk) - getSelf(l, rl)
632                             - get2Body(i, ri, j, rj) - get2Body(i, ri, k, rk) - get2Body(i, ri, l, rl)
633                             - get2Body(j, rj, k, rk) - get2Body(j, rj, l, rl) - get2Body(k, rk, l, rl)
634                             - get3Body(residues, i, ri, j, rj, k, rk) - get3Body(residues, i, ri, j, rj, l, rl)
635                             - get3Body(residues, i, ri, k, rk, l, rl) - get3Body(residues, j, rj, k, rk, l, rl);
636             energy = rO.currentEnergy(residues) + subtract;
637 
638             if (potentialIsOpenMM && energy < ommRecalculateThreshold) {
639                 logger.warning(format(
640                         " Experimental: re-computing quad energy %s-%d %s-%d %s-%d %s-%d using Force Field X",
641                         residues[i], ri, residues[j], rj, residues[k], rk, residues[l], rl));
642                 energy = rO.currentFFXPE() + subtract;
643             }
644             if (energy < singularityThreshold) {
645                 String message = format(
646                         " Rejecting quad energy for %s-%d %s-%d %s-%d %s-%d is %10.5g << %10f, "
647                                 + "likely in error.", residues[i], ri, residues[j], rj, residues[k], rk, residues[l],
648                         rl, energy, singularityThreshold);
649                 logger.warning(message);
650                 throw new EnergyException(message);
651             }
652 
653         } finally {
654             // Revert if the currentEnergy call throws an exception.
655             turnOffResidue(residues[i]);
656             turnOffResidue(residues[j]);
657             turnOffResidue(residues[k]);
658             turnOffResidue(residues[l]);
659         }
660         return energy;
661     }
662 
663     /**
664      * Computes a self energy, defined as energy with all side-chains but one turned off, minus the
665      * backbone energy.
666      * <p>
667      * If a residue has multiple titration states represented by its set of rotamers, then a
668      * pH-dependent bias is included.
669      *
670      * @param residues Residues under optimization.
671      * @param i        A residue index.
672      * @param ri       A rotamer index for residue i.
673      * @return Eself(ri)=E1(ri)-Eenv/bb.
674      */
675     public double computeSelfEnergy(Residue[] residues, int i, int ri) {
676         rO.turnOffAllResidues(residues);
677         rO.turnOnResidue(residues[i], ri);
678         double KpH = rO.getPHRestraint();
679         double energy;
680         try {
681             if (algorithmListener != null) {
682                 algorithmListener.algorithmUpdate(molecularAssembly);
683             }
684             energy = rO.currentEnergy(residues) - backboneEnergy;
685             if (potentialIsOpenMM && energy < ommRecalculateThreshold) {
686                 logger.warning(
687                         format(" Experimental: re-computing self energy %s-%d using Force Field X", residues[i],
688                                 ri));
689                 energy = rO.currentFFXPE() - backboneEnergy;
690             }
691             if (energy < singularityThreshold) {
692                 String message = format(
693                         " Rejecting self energy for %s-%d is %10.5g << %10f, " + "likely in error.", residues[i],
694                         ri, energy, singularityThreshold);
695                 logger.warning(message);
696                 throw new EnergyException(message);
697             }
698         } finally {
699             rO.turnOffResidue(residues[i]);
700         }
701 
702         Rotamer[] rotamers = residues[i].getRotamers();
703 
704         if (rotamers[ri].isTitrating) {
705             double bias = rotamers[ri].getRotamerPhBias();
706 
707             double pH = rO.getPH();
708             String name = rotamers[ri].getName();
709             double pHrestraint = 0;
710 
711             switch (name) {
712                 case "ASP":
713                     if (pH <= 3.94) {
714                         pHrestraint = 0.5 * KpH * Math.pow(pH - 3.94, 2);
715                     }
716                     break;
717                 case "ASH":
718                     if (pH >= 3.94) {
719                         pHrestraint = 0.5 * KpH * Math.pow(pH - 3.94, 2);
720                     }
721                     break;
722                 case "GLU":
723                     if (pH <= 4.25) {
724                         pHrestraint = 0.5 * KpH * Math.pow(pH  - 4.25, 2);
725                     }
726                     break;
727                 case "GLH":
728                     if (pH >= 4.25) {
729                         pHrestraint = 0.5 * KpH * Math.pow(pH  - 4.25, 2);
730                     }
731                     break;
732                 case "HIS":
733                     if (pH >= 6.6) {
734                         pHrestraint = 0.5 * KpH * Math.pow(pH - 6.6, 2);
735                     }
736                     break;
737                 case "HID":
738                     if (pH <= 7.0) {
739                         pHrestraint = 0.5 * KpH * Math.pow(pH - 7.0, 2);
740                     }
741                     break;
742                 case "HIE":
743                     if(pH <= 6.6){
744                         pHrestraint = 0.5 * KpH * Math.pow(pH - 6.6, 2);
745                     }
746                     break;
747                 case "LYD":
748                     if(pH <= 10.4){
749                         pHrestraint = 0.5 * KpH * Math.pow(pH - 10.4, 2);
750                     }
751                     break;
752                 case "LYS":
753                     if(pH >= 10.4){
754                         pHrestraint = 0.5 * KpH * Math.pow(pH - 10.4, 2);
755                     }
756                     break;
757                 case "CYD":
758                     if(pH - 8.55 <= 0){
759                         pHrestraint = 0.5 * KpH * Math.pow(pH - 8.55,2);
760                     }
761                     break;
762                 case "CYS":
763                     if(pH - 8.55 >= 0){
764                         pHrestraint = 0.5 * KpH * Math.pow(pH - 8.55,2);
765                     }
766                     break;
767                 default:
768                     break;
769             }
770 
771             if (logger.isLoggable(Level.FINE)) {
772                 logger.fine(format(" %s Self-Energy %16.8f = FF %16.8f - BB %16.8f + Ph Bias %16.8f + pHrestraint %16.8f ",
773                         rotamers[ri].getName(), energy + bias + pHrestraint, energy + backboneEnergy, backboneEnergy, bias, pHrestraint));
774             }
775             energy += bias + pHrestraint;
776         }
777         return energy;
778     }
779 
780 
781     /**
782      * Compute the total rotamer Ph bias for an array of residues.
783      *
784      * @param residues The array of residues.
785      * @param rotamers The array of rotamer indices for each residue.
786      * @return The total Ph bias.
787      */
788     public double getTotalRotamerPhBias(List<Residue> residues, int[] rotamers, double pH, double KpH) {
789         double total = 0.0;
790         int n = residues.size();
791         for (int i = 0; i < n; i++) {
792             Rotamer[] rot = residues.get(i).getRotamers();
793             int ri = rotamers[i];
794             double pHrestraint = 0;
795             if (rot[ri].isTitrating) {
796                 switch (rot[ri].getName()) {
797                     case "ASP":
798                         if (pH <= 3.94) {
799                             pHrestraint = 0.5 * KpH * Math.pow(pH - 3.94, 2);
800                         }
801                         break;
802                     case "ASH":
803                         if (pH >= 3.94) {
804                             pHrestraint = 0.5 * KpH * Math.pow(pH - 3.94, 2);
805                         }
806                         break;
807                     case "GLU":
808                         if (pH <= 4.25) {
809                             pHrestraint = 0.5 * KpH * Math.pow(pH  - 4.25, 2);
810                         }
811                         break;
812                     case "GLH":
813                         if (pH >= 4.25) {
814                             pHrestraint = 0.5 * KpH * Math.pow(pH  - 4.25, 2);
815                         }
816                         break;
817                     case "HIS":
818                         if (pH >= 6.6) {
819                             pHrestraint = 0.5 * KpH * Math.pow(pH - 6.6, 2);
820                         }
821                         break;
822                     case "HID":
823                         if (pH <= 7.0) {
824                             pHrestraint = 0.5 * KpH * Math.pow(pH - 7.0, 2);
825                         }
826                         break;
827                     case "HIE":
828                         if(pH <= 6.6){
829                             pHrestraint = 0.5 * KpH * Math.pow(pH - 6.6, 2);
830                         }
831                         break;
832                     case "LYD":
833                         if(pH <= 10.4){
834                             pHrestraint = 0.5 * KpH * Math.pow(pH - 10.4, 2);
835                         }
836                         break;
837                     case "LYS":
838                         if(pH >= 10.4){
839                             pHrestraint = 0.5 * KpH * Math.pow(pH - 10.4, 2);
840                         }
841                         break;
842                     case "CYD":
843                         if(pH - 8.55 <= 0){
844                             pHrestraint = 0.5 * KpH * Math.pow(pH - 8.55,2);
845                         }
846                         break;
847                     case "CYS":
848                         if(pH - 8.55 >= 0){
849                             pHrestraint = 0.5 * KpH * Math.pow(pH - 8.55,2);
850                         }
851                         break;
852                     default:
853                         break;
854                 }
855                 total += rot[ri].getRotamerPhBias() + pHrestraint;
856             }
857         }
858         return total;
859     }
860 
861 
862     /**
863      * Return a previously computed 2-body energy.
864      *
865      * @param i  Residue i.
866      * @param ri Rotamer ri of residue i.
867      * @param j  Residue j.
868      * @param rj Rotamer rj of residue j.
869      * @return The 2-Body energy.
870      */
871     public double get2Body(int i, int ri, int j, int rj) {
872         // Ensure i < j.
873         if (j < i) {
874             int ii = i;
875             int iri = ri;
876             i = j;
877             ri = rj;
878             j = ii;
879             rj = iri;
880         }
881         try {
882             // Find where j is in i's neighbor list (and thus the 2-body energy matrix).
883             int[] nI = resNeighbors[i];
884             int indJ = -1;
885             for (int l = 0; l < nI.length; l++) {
886                 if (nI[l] == j) {
887                     indJ = l;
888                     break;
889                 }
890             }
891             if (indJ == -1) {
892                 logger.fine(format(" Residue %d not found in neighbors of %d; assumed past cutoff.", j, i));
893                 return 0;
894             } else {
895                 return twoBodyEnergy[i][ri][indJ][rj];
896             }
897         } catch (NullPointerException npe) {
898             logger.info(format(" NPE for 2-body energy (%3d,%2d) (%3d,%2d).", i, ri, j, rj));
899             throw npe;
900         }
901     }
902 
903     /**
904      * Return a previously computed 3-body energy.
905      *
906      * @param i        Residue i.
907      * @param ri       Rotamer ri of residue i.
908      * @param j        Residue j.
909      * @param rj       Rotamer rj of residue j.
910      * @param k        Residue k.
911      * @param rk       Rotamer rk of residue k.
912      * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
913      * @return The 3-Body energy.
914      */
915     public double get3Body(Residue[] residues, int i, int ri, int j, int rj, int k, int rk) {
916         if (!threeBodyTerm) {
917             return 0.0;
918         }
919         // Ensure i < j and j < k.
920         if (j < i) {
921             int ii = i;
922             int iri = ri;
923             i = j;
924             ri = rj;
925             j = ii;
926             rj = iri;
927         }
928         if (k < i) {
929             int ii = i;
930             int iri = ri;
931             i = k;
932             ri = rk;
933             k = ii;
934             rk = iri;
935         }
936         if (k < j) {
937             int jj = j;
938             int jrj = rj;
939             j = k;
940             rj = rk;
941             k = jj;
942             rk = jrj;
943         }
944 
945         // Find where j is in i's neighbor list, and where k is in j's neighbor list.
946         int[] nI = resNeighbors[i];
947         int indJ = -1;
948         for (int l = 0; l < nI.length; l++) {
949             if (nI[l] == j) {
950                 indJ = l;
951                 break;
952             }
953         }
954 
955         int[] nJ = resNeighbors[j];
956         int indK = -1;
957         for (int l = 0; l < nJ.length; l++) {
958             if (nJ[l] == k) {
959                 indK = l;
960                 break;
961             }
962         }
963 
964         // i,j,k: Indices in the current Residue array.
965         // indJ, indK: Index of j in i's neighbor list, index of k in j's neighbor list.
966         // indexI, indexJ, indexK: Indices in allResiduesList.
967         int indexI = allResiduesList.indexOf(residues[i]);
968         int indexJ = allResiduesList.indexOf(residues[j]);
969         int indexK = allResiduesList.indexOf(residues[k]);
970         if (dM.checkTriDistThreshold(indexI, ri, indexJ, rj, indexK, rk)) {
971             return 0;
972         } else {
973             try {
974                 return threeBodyEnergy[i][ri][indJ][rj][indK][rk];
975             } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) {
976                 String message = format(
977                         " Could not find an energy for 3-body energy (%3d,%2d) (%3d,%2d) (%3d,%2d)", i, ri, j,
978                         rj, k, rk);
979                 logger.warning(message);
980                 throw ex;
981             }
982         }
983     }
984 
985     public double getBackboneEnergy() {
986         return backboneEnergy;
987     }
988 
989     public void setBackboneEnergy(double backboneEnergy) {
990         this.backboneEnergy = backboneEnergy;
991     }
992 
993     public Map<Integer, Integer[]> getFourBodyEnergyMap() {
994         return fourBodyEnergyMap;
995     }
996 
997     /**
998      * Return a previously computed self-energy.
999      *
1000      * @param i  Residue i.
1001      * @param ri Rotamer ri of residue i.
1002      * @return The self-energy.
1003      */
1004     public double getSelf(int i, int ri) {
1005         try {
1006             return selfEnergy[i][ri];
1007         } catch (NullPointerException npe) {
1008             logger.info(format(" NPE for self energy (%3d,%2d).", i, ri));
1009             throw npe;
1010         }
1011     }
1012 
1013     /**
1014      * Return a previously computed self-energy.
1015      *
1016      * @param i  Residue i.
1017      * @param ri Rotamer ri of residue i.
1018      * @return The self-energy.
1019      */
1020     public double getSelf(int i, int ri, Rotamer rot, boolean excludeFMod) {
1021         try {
1022             double totalSelf;
1023             if (rot.isTitrating && excludeFMod) {
1024                 String name = rot.getName();
1025                 double pHRestraint = 0;
1026                 switch (name) {
1027                     case "ASP":
1028                         if (rO.getPH() <= 3.94) {
1029                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 3.94, 2);
1030                         }
1031                         break;
1032                     case "ASH":
1033                         if (rO.getPH() >= 3.94) {
1034                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 3.94, 2);
1035                         }
1036                         break;
1037                     case "GLU":
1038                         if (rO.getPH() <= 4.25) {
1039                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 4.25, 2);
1040                         }
1041                         break;
1042                     case "GLH":
1043                         if (rO.getPH() >= 4.25) {
1044                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 4.25, 2);
1045                         }
1046                         break;
1047                     case "HIS":
1048                         if (rO.getPH() >= 6.6) {
1049                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 6.6, 2);
1050                         }
1051                         break;
1052                     case "HID":
1053                         if (rO.getPH() <= 7.0) {
1054                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 7.0, 2);
1055                         }
1056                         break;
1057                     case "HIE":
1058                         if(rO.getPH() <= 6.6){
1059                             pHRestraint = 0.5 * rO.getPHRestraint() * Math.pow(rO.getPH() - 6.6, 2);
1060                         }
1061                         break;
1062                     case "LYD":
1063                         if(rO.getPH() <= 10.4){
1064                            pHRestraint = 0.5*rO.getPHRestraint()*Math.pow(rO.getPH()- 10.4,2);
1065                         }
1066                         break;
1067                     case "LYS":
1068                         if(rO.getPH() >= 10.4){
1069                             pHRestraint = 0.5*rO.getPHRestraint()*Math.pow(rO.getPH()- 10.4,2);
1070                         }
1071                         break;
1072                     case "CYD":
1073                         if(rO.getPH() - 8.55 <= 0){
1074                             pHRestraint = 0.5*rO.getPHRestraint()*Math.pow(rO.getPH()- 8.55,2);
1075                         }
1076                         break;
1077                     case "CYS":
1078                         if(rO.getPH() - 8.55 >= 0){
1079                             pHRestraint = 0.5*rO.getPHRestraint()*Math.pow(rO.getPH()- 8.55,2);
1080                         }
1081                         break;
1082                     default:
1083                         break;
1084                 }
1085                 totalSelf = selfEnergy[i][ri] - rot.getRotamerPhBias() - pHRestraint;
1086             } else {
1087                 totalSelf = selfEnergy[i][ri];
1088             }
1089             return totalSelf;
1090         } catch (NullPointerException npe) {
1091             logger.info(format(" NPE for self energy (%3d,%2d).", i, ri));
1092             throw npe;
1093         }
1094     }
1095 
1096 
1097     public Map<Integer, Integer[]> getSelfEnergyMap() {
1098         return selfEnergyMap;
1099     }
1100 
1101     public Map<Integer, Integer[]> getThreeBodyEnergyMap() {
1102         return threeBodyEnergyMap;
1103     }
1104 
1105     public Map<Integer, Integer[]> getTwoBodyEnergyMap() {
1106         return twoBodyEnergyMap;
1107     }
1108 
1109     public int loadEnergyRestart(File restartFile, Residue[] residues) {
1110         return loadEnergyRestart(restartFile, residues, -1, null);
1111     }
1112 
1113     public int loadEnergyRestart(File restartFile, Residue[] residues, int boxIteration,
1114                                  int[] cellIndices) {
1115         try {
1116             int nResidues = residues.length;
1117             Path path = Paths.get(restartFile.getCanonicalPath());
1118             List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
1119             List<String> linesThisBox = new ArrayList<>();
1120 
1121             try {
1122                 backboneEnergy = rO.computeBackboneEnergy(residues);
1123             } catch (ArithmeticException ex) {
1124                 logger.severe(
1125                         format(" Exception %s in calculating backbone energy; FFX shutting down.", ex));
1126             }
1127             rO.logIfRank0(format("\n Backbone energy:  %s\n", rO.formatEnergy(backboneEnergy)));
1128 
1129             if (usingBoxOptimization && boxIteration >= 0) {
1130                 boolean foundBox = false;
1131                 for (int i = 0; i < lines.size(); i++) {
1132                     String line = lines.get(i);
1133                     if (line.startsWith("Box")) {
1134                         String[] tok = line.replaceAll("Box", "").replaceAll(":", ",").replaceAll(" ", "")
1135                                 .split(",");
1136                         int readIteration = Integer.parseInt(tok[0]);
1137                         int readCellIndexX = Integer.parseInt(tok[1]);
1138                         int readCellIndexY = Integer.parseInt(tok[2]);
1139                         int readCellIndexZ = Integer.parseInt(tok[3]);
1140                         if (readIteration == boxIteration && readCellIndexX == cellIndices[0]
1141                                 && readCellIndexY == cellIndices[1] && readCellIndexZ == cellIndices[2]) {
1142                             foundBox = true;
1143                             for (int j = i + 1; j < lines.size(); j++) {
1144                                 String l = lines.get(j);
1145                                 if (l.startsWith("Box")) {
1146                                     break;
1147                                 }
1148                                 linesThisBox.add(l);
1149                             }
1150                             break;
1151                         }
1152                     }
1153                 }
1154                 if (!foundBox) {
1155                     rO.logIfRank0(format(" Didn't find restart energies for Box %d: %d,%d,%d", boxIteration,
1156                             cellIndices[0], cellIndices[1], cellIndices[2]));
1157                     return 0;
1158                 } else if (linesThisBox.isEmpty()) {
1159                     return 0;
1160                 } else {
1161                     lines = linesThisBox;
1162                 }
1163             }
1164 
1165             List<String> singleLines = new ArrayList<>();
1166             List<String> pairLines = new ArrayList<>();
1167             List<String> tripleLines = new ArrayList<>();
1168             for (String line : lines) {
1169                 String[] tok = line.split("\\s");
1170                 if (tok[0].startsWith("Self")) {
1171                     singleLines.add(line);
1172                 } else if (tok[0].startsWith("Pair")) {
1173                     pairLines.add(line);
1174                 } else if (tok[0].startsWith("Triple")) {
1175                     tripleLines.add(line);
1176                 }
1177             }
1178             int loaded = 0;
1179             if (!tripleLines.isEmpty()) {
1180                 loaded = 3;
1181             } else if (!pairLines.isEmpty()) {
1182                 loaded = 2;
1183             } else if (!singleLines.isEmpty()) {
1184                 loaded = 1;
1185             } else {
1186                 logger.warning(
1187                         format(" Empty or unreadable energy restart file: %s.", restartFile.getCanonicalPath()));
1188             }
1189             if (loaded >= 1) {
1190                 boolean reverseMap = true;
1191                 HashMap<String, Integer> reverseJobMapSingles = allocateSelfJobMap(residues, nResidues,
1192                         reverseMap);
1193                 // fill in self-energies from file while removing the corresponding jobs from selfEnergyMap
1194                 for (String line : singleLines) {
1195                     try {
1196                         String[] tok = line.replace(",", "").replace(":", "").split("\\s+");
1197                         int i;
1198                         if (tok[1].contains("-")) {
1199                             i = nameToNumber(tok[1], residues);
1200                         } else {
1201                             i = Integer.parseInt(tok[1]);
1202                         }
1203                         int ri = Integer.parseInt(tok[2]);
1204                         double energy = Double.parseDouble(tok[3]);
1205                         try {
1206                             setSelf(i, ri, energy);
1207                             if (verbose) {
1208                                 rO.logIfRank0(format(" From restart file: Self energy %3d (%8s,%2d): %s", i,
1209                                         residues[i].toFormattedString(false, true), ri, rO.formatEnergy(energy)));
1210                             }
1211                         } catch (Exception e) {
1212                             if (verbose) {
1213                                 rO.logIfRank0(format(" Restart file out-of-bounds index: %s", line));
1214                             }
1215                         }
1216                         // remove that job from the pool
1217                         String revKey = format("%d %d", i, ri);
1218                         selfEnergyMap.remove(reverseJobMapSingles.get(revKey));
1219                     } catch (NumberFormatException ex) {
1220                         logger.log(Level.WARNING, format(" Unparsable line in energy restart file: \n%s", line),
1221                                 ex);
1222                     }
1223                 }
1224                 rO.logIfRank0(" Loaded self energies from restart file.");
1225 
1226                 // Pre-Prune if self-energy is Double.NaN.
1227                 eR.prePruneSelves(residues);
1228 
1229                 // prune singles
1230                 if (pruneClashes) {
1231                     eR.pruneSingleClashes(residues);
1232                 }
1233             }
1234 
1235             // Remap to sequential integer keys.
1236             condenseEnergyMap(selfEnergyMap);
1237 
1238             if (loaded >= 2) {
1239                 if (!selfEnergyMap.isEmpty()) {
1240                     rO.logIfRank0(
1241                             " Double-check that parameters match original run due to missing self-energies.");
1242                 }
1243                 boolean reverseMap = true;
1244                 HashMap<String, Integer> reverseJobMapPairs = allocate2BodyJobMap(residues, nResidues,
1245                         reverseMap);
1246                 // fill in pair-energies from file while removing the corresponding jobs from
1247                 // twoBodyEnergyMap
1248                 for (String line : pairLines) {
1249                     try {
1250                         String[] tok = line.replace(",", "").replace(":", "").split("\\s+");
1251                         int i;
1252                         if (tok[1].contains("-")) {
1253                             i = nameToNumber(tok[1], residues);
1254                         } else {
1255                             i = Integer.parseInt(tok[1]);
1256                         }
1257                         int ri = Integer.parseInt(tok[2]);
1258                         int j;
1259                         if (tok[3].contains("-")) {
1260                             j = nameToNumber(tok[3], residues);
1261                         } else {
1262                             j = Integer.parseInt(tok[3]);
1263                         }
1264                         int rj = Integer.parseInt(tok[4]);
1265                         double energy = Double.parseDouble(tok[5]);
1266                         try {
1267                             // When a restart file is generated using a large cutoff, but a new simulation is
1268                             // being done with a smaller cutoff, the two-body distance needs to be checked. If the two-body
1269                             // distance is larger than the cutoff, then the two residues are not considered
1270                             // 'neighbors' so that pair should not be added to the pairs map.
1271                             if (rO.checkNeighboringPair(i, j)) {
1272                                 // If inside the cutoff, set energy to previously computed value.
1273                                 // Gather distances and indices for printing.
1274                                 Residue residueI = residues[i];
1275                                 Residue residueJ = residues[j];
1276                                 int indexI = allResiduesList.indexOf(residueI);
1277                                 int indexJ = allResiduesList.indexOf(residueJ);
1278                                 if (!dM.checkPairDistThreshold(indexI, ri, indexJ, rj)) {
1279                                     set2Body(i, ri, j, rj, energy);
1280 
1281                                     double resDist = dM.getResidueDistance(indexI, ri, indexJ, rj);
1282                                     String resDistString = "large";
1283                                     if (resDist < Double.MAX_VALUE) {
1284                                         resDistString = format("%5.3f", resDist);
1285                                     }
1286 
1287                                     double dist = dM.checkDistMatrix(indexI, ri, indexJ, rj);
1288                                     String distString = "     large";
1289                                     if (dist < Double.MAX_VALUE) {
1290                                         distString = format("%10.3f", dist);
1291                                     }
1292 
1293                                     logger.fine(format(" Pair %8s %-2d, %8s %-2d: %s at %s Ang (%s Ang by residue).",
1294                                             residueI.toFormattedString(false, true), ri,
1295                                             residueJ.toFormattedString(false, true), rj,
1296                                             rO.formatEnergy(get2Body(i, ri, j, rj)), distString, resDistString));
1297                                 }
1298                             } else {
1299                                 logger.fine(format(
1300                                         "Ignoring a pair-energy from outside the cutoff: 2-energy [(%8s,%2d),(%8s,%2d)]: %12.4f",
1301                                         residues[i].toFormattedString(false, true), ri,
1302                                         residues[j].toFormattedString(false, true), rj, energy));
1303                             }
1304 
1305                             if (verbose) {
1306                                 rO.logIfRank0(
1307                                         format(" From restart file: Pair energy [(%8s,%2d),(%8s,%2d)]: %12.4f",
1308                                                 residues[i].toFormattedString(false, true), ri,
1309                                                 residues[j].toFormattedString(false, true), rj, energy));
1310                             }
1311                         } catch (Exception e) {
1312                             if (verbose) {
1313                                 rO.logIfRank0(format(" Restart file out-of-bounds index: %s", line));
1314                             }
1315                         }
1316                         // remove that job from the pool
1317                         String revKey = format("%d %d %d %d", i, ri, j, rj);
1318                         twoBodyEnergyMap.remove(reverseJobMapPairs.get(revKey));
1319                     } catch (NumberFormatException ex) {
1320                         logger.log(Level.WARNING, format("Unparsable line in energy restart file: \n%s", line),
1321                                 ex);
1322                     }
1323                 }
1324                 rO.logIfRank0(" Loaded 2-body energies from restart file.");
1325 
1326                 // Pre-Prune if pair-energy is Double.NaN.
1327                 eR.prePrunePairs(residues);
1328 
1329                 // prune pairs
1330                 if (prunePairClashes) {
1331                     eR.prunePairClashes(residues);
1332                 }
1333             }
1334 
1335             // Remap to sequential integer keys.
1336             condenseEnergyMap(twoBodyEnergyMap);
1337 
1338             if (loaded >= 3) {
1339                 if (!twoBodyEnergyMap.isEmpty()) {
1340                     if (master) {
1341                         logger.warning(
1342                                 "Double-check that parameters match original run!  Found trimers in restart file, but pairs job queue is non-empty.");
1343                     }
1344                 }
1345                 boolean reverseMap = true;
1346                 HashMap<String, Integer> reverseJobMapTrimers = allocate3BodyJobMap(residues, nResidues,
1347                         reverseMap);
1348 
1349                 // fill in 3-Body energies from file while removing the corresponding jobs from
1350                 // threeBodyEnergyMap
1351                 for (String line : tripleLines) {
1352                     try {
1353                         String[] tok = line.replace(",", "").replace(":", "").split("\\s+");
1354                         int i;
1355                         if (tok[1].contains("-")) {
1356                             i = nameToNumber(tok[1], residues);
1357                         } else {
1358                             i = Integer.parseInt(tok[1]);
1359                         }
1360                         int ri = Integer.parseInt(tok[2]);
1361                         int j;
1362                         if (tok[3].contains("-")) {
1363                             j = nameToNumber(tok[3], residues);
1364                         } else {
1365                             j = Integer.parseInt(tok[3]);
1366                         }
1367                         int rj = Integer.parseInt(tok[4]);
1368                         int k;
1369                         if (tok[5].contains("-")) {
1370                             k = nameToNumber(tok[5], residues);
1371                         } else {
1372                             k = Integer.parseInt(tok[5]);
1373                         }
1374                         int rk = Integer.parseInt(tok[6]);
1375                         double energy = Double.parseDouble(tok[7]);
1376 
1377                         try {
1378               /*
1379                 When a restart file is generated using a large cutoff, but a new simulation is
1380                 being done with a smaller cutoff, the three-body distance needs to be checked. If the
1381                 three-body distance is larger than the cutoff, then the three residues are not considered
1382                 'neighbors' so that triple should not be added to the pairs map.
1383                */
1384                             if (rO.checkNeighboringTriple(i, j, k)) {
1385                                 // If within the cutoff, the energy should be set to the previously calculated
1386                                 // energy.
1387                                 Residue residueI = residues[i];
1388                                 Residue residueJ = residues[j];
1389                                 Residue residueK = residues[k];
1390                                 int indexI = allResiduesList.indexOf(residueI);
1391                                 int indexJ = allResiduesList.indexOf(residueJ);
1392                                 int indexK = allResiduesList.indexOf(residueK);
1393                                 if (!dM.checkTriDistThreshold(indexI, ri, indexJ, rj, indexK, rk)) {
1394                                     set3Body(residues, i, ri, j, rj, k, rk, energy);
1395 
1396                                     double resDist = dM.get3BodyResidueDistance(indexI, ri, indexJ, rj, indexK, rk);
1397                                     String resDistString = "     large";
1398                                     if (resDist < Double.MAX_VALUE) {
1399                                         resDistString = format("%5.3f", resDist);
1400                                     }
1401 
1402                                     double rawDist = dM.getRawNBodyDistance(indexI, ri, indexJ, rj, indexK, rk);
1403                                     String distString = "     large";
1404                                     if (rawDist < Double.MAX_VALUE) {
1405                                         distString = format("%10.3f", rawDist);
1406                                     }
1407 
1408                                     logger.fine(format(
1409                                             " 3-Body %8s %-2d, %8s %-2d, %8s %-2d: %s at %s Ang (%s Ang by residue).",
1410                                             residueI.toFormattedString(false, true), ri,
1411                                             residueJ.toFormattedString(false, true), rj,
1412                                             residueK.toFormattedString(false, true), rk,
1413                                             rO.formatEnergy(get3Body(residues, i, ri, j, rj, k, rk)), distString,
1414                                             resDistString));
1415                                 }
1416                             } else {
1417                                 logger.fine(format(
1418                                         "Ignoring a triple-energy from outside the cutoff: 3-Body %8s %-2d, %8s %-2d, %8s %-2d: %s",
1419                                         residues[i].toFormattedString(false, true), ri,
1420                                         residues[j].toFormattedString(false, true), rj,
1421                                         residues[k].toFormattedString(false, true), rk,
1422                                         rO.formatEnergy(get3Body(residues, i, ri, j, rj, k, rk))));
1423                             }
1424                         } catch (ArrayIndexOutOfBoundsException ex) {
1425                             if (verbose) {
1426                                 rO.logIfRank0(format(" Restart file out-of-bounds index: %s", line));
1427                             }
1428                         } catch (NullPointerException npe) {
1429                             if (verbose) {
1430                                 rO.logIfRank0(format(" NPE in loading 3-body energies: pruning "
1431                                                 + "likely changed! 3-body %s-%d %s-%d %s-%d",
1432                                         residues[i].toFormattedString(false, true), ri, residues[j], rj, residues[k],
1433                                         rk));
1434                             }
1435                         }
1436                         if (verbose) {
1437                             rO.logIfRank0(
1438                                     format(" From restart file: Trimer energy %3d %-2d, %3d %-2d, %3d %-2d: %s", i, ri,
1439                                             j, rj, k, rk, rO.formatEnergy(energy)));
1440                         }
1441                         // Remove that job from the pool.
1442                         String revKey = format("%d %d %d %d %d %d", i, ri, j, rj, k, rk);
1443                         threeBodyEnergyMap.remove(reverseJobMapTrimers.get(revKey));
1444                     } catch (NumberFormatException ex) {
1445                         logger.log(Level.WARNING, format("Unparsable line in energy restart file: \n%s", line),
1446                                 ex);
1447                     }
1448                 }
1449                 rO.logIfRank0(" Loaded trimer energies from restart file.");
1450             }
1451 
1452             // Remap to sequential integer keys.
1453             condenseEnergyMap(threeBodyEnergyMap);
1454 
1455             return loaded;
1456         } catch (IOException ex) {
1457             logger.log(Level.WARNING, "Exception while loading energy restart file.", ex);
1458         }
1459 
1460         return 0;
1461     }
1462 
1463     /**
1464      * Return the lowest pair-energy for residue (i,ri) with residue j.
1465      *
1466      * @param residues Residue array.
1467      * @param i        Residue i index.
1468      * @param ri       Residue i rotamer index.
1469      * @param j        Residue j index.
1470      * @return Lowest pair energy.
1471      */
1472     public double lowestPairEnergy(Residue[] residues, int i, int ri, int j) {
1473         if (residues == null) {
1474             return 0.0;
1475         }
1476         int n = residues.length;
1477         if (i < 0 || i >= n) {
1478             return 0.0;
1479         }
1480         if (j < 0 || j >= n) {
1481             return 0.0;
1482         }
1483 
1484         Rotamer[] rotamers = residues[j].getRotamers();
1485         int nr = rotamers.length;
1486         double energy = Double.MAX_VALUE;
1487         for (int jr = 0; jr < nr; jr++) {
1488             try {
1489                 double e = get2Body(i, ri, j, jr);
1490                 if (e < energy) {
1491                     energy = e;
1492                 }
1493             } catch (Exception e) {
1494                 // continue.
1495             }
1496         }
1497         return energy;
1498     }
1499 
1500     /**
1501      * Return the lowest self-energy for residue i.
1502      *
1503      * @param residues Array if residues.
1504      * @param i        Index of residue i.
1505      * @return Returns the lowest self-energy for residue i.
1506      */
1507     public double lowestSelfEnergy(Residue[] residues, int i) {
1508         if (residues == null) {
1509             return 0.0;
1510         }
1511         int n = residues.length;
1512         if (i < 0 || i >= n) {
1513             return 0.0;
1514         }
1515         Rotamer[] rotamers = residues[i].getRotamers();
1516         int nr = rotamers.length;
1517         double energy = Double.MAX_VALUE;
1518         for (int ni = 0; ni < nr; ni++) {
1519             try {
1520                 double e = getSelf(i, ni);
1521                 if (e < energy) {
1522                     energy = e;
1523                 }
1524             } catch (Exception e) {
1525                 // continue
1526             }
1527         }
1528         return energy;
1529     }
1530 
1531     /**
1532      * Calculates the minimum and maximum summations over additional residues for some pair ri-rj.
1533      *
1534      * @param residues Residues under consideration.
1535      * @param minMax   Result array: 0 is min summation, 1 max summation.
1536      * @param i        Residue i.
1537      * @param ri       Rotamer for residue i.
1538      * @param j        Residue j!=i.
1539      * @param rj       Rotamer for residue j.
1540      * @return False if ri-rj always clashes with other residues.
1541      * @throws IllegalArgumentException If ri, rj, or ri-rj eliminated.
1542      */
1543     public boolean minMaxE2(Residue[] residues, double[] minMax, int i, int ri, int j, int rj)
1544             throws IllegalArgumentException {
1545         Residue resi = residues[i];
1546         Residue resj = residues[j];
1547         if (eR.check(i, ri) || eR.check(j, rj) || eR.check(i, ri, j, rj)) {
1548             throw new IllegalArgumentException(
1549                     format(" Called for minMaxE2 on an eliminated pair %s-%d %s-%d",
1550                             resi.toFormattedString(false, true), ri, resj.toFormattedString(false, true), rj));
1551         }
1552 
1553         // Minimum summation over third residues k.
1554         minMax[0] = 0;
1555         // Maximum summation over third residues k.
1556         minMax[1] = 0;
1557 
1558         int nRes = residues.length;
1559         for (int k = 0; k < nRes; k++) {
1560             if (k == i || k == j) {
1561                 continue;
1562             }
1563             Residue resk = residues[k];
1564             Rotamer[] rotsk = resk.getRotamers();
1565             int lenrk = rotsk.length;
1566             double[] minMaxK = new double[2];
1567             minMaxK[0] = Double.MAX_VALUE;
1568             minMaxK[1] = Double.MIN_VALUE;
1569 
1570             for (int rk = 0; rk < lenrk; rk++) {
1571                 if (eR.check(k, rk)) {
1572                     // Not a valid part of phase space.
1573                     continue;
1574                 }
1575                 if (eR.check(i, ri, k, rk) || eR.check(j, rj, k, rk)) {
1576                     // Not implemented: check(i, ri, j, rj, k, rk).
1577 
1578                     // i,ri or j,rj clashes with this rotamer, max will be NaN.
1579                     // Minimum for this rk will be a clash, which is never a minimum.
1580                     minMaxK[1] = Double.NaN;
1581                 } else {
1582 
1583                     // Min and max summations over 4th residues l, plus the ri-rk and rj-rk interactions.
1584                     // If no 3-body term, just the ri-rk and rj-rk interactions.
1585                     double currentMin = get2Body(i, ri, k, rk) + get2Body(j, rj, k, rk);
1586                     double currentMax = currentMin;
1587                     if (threeBodyTerm) {
1588                         // If the 3-Body eliminated, would fill max to Double.NaN.
1589                         currentMin += get3Body(residues, i, ri, j, rj, k, rk);
1590                         currentMax = currentMin;
1591 
1592                         // Obtain min and max summations over l.
1593                         double[] minMaxTriple = new double[2];
1594                         if (minMaxE3(residues, minMaxTriple, i, ri, j, rj, k, rk)) {
1595                             // A non-finite triples minimum should have the code taking the else branch.
1596                             assert (Double.isFinite(minMaxTriple[0]) && minMaxTriple[0] != Double.MAX_VALUE);
1597 
1598                             // Add the min and max summations over all 4th residues l.
1599                             currentMin += minMaxTriple[0];
1600 
1601                             if (Double.isFinite(currentMax) && Double.isFinite(minMaxTriple[1])) {
1602                                 currentMax += minMaxTriple[1];
1603                             } else {
1604                                 currentMax = Double.NaN;
1605                             }
1606                         } else {
1607                             // i, ri, j, rj, k, rk creates an inevitable clash with some residue l.
1608                             currentMin = Double.NaN;
1609                             currentMax = Double.NaN;
1610                         }
1611                     }
1612 
1613                     assert (threeBodyTerm || currentMax == currentMin);
1614 
1615                     // Now check if rk displaces previously searched rk for min/max over this k.
1616                     if (Double.isFinite(currentMin) && currentMin < minMaxK[0]) {
1617                         // rk has a more favorable minimum than previously searched rk.
1618                         minMaxK[0] = currentMin;
1619                     } // Else, no new minimum found.
1620 
1621                     if (Double.isFinite(currentMax) && Double.isFinite(minMaxK[1])) {
1622                         // rk has a less favorable maximum than previously searched rk.
1623                         minMaxK[1] = Math.max(currentMax, minMaxK[1]);
1624                     } else {
1625                         // Our maximum is a NaN.
1626                         minMaxK[1] = Double.NaN;
1627                     }
1628                 }
1629             }
1630 
1631             if (Double.isFinite(minMaxK[0])) {
1632                 // Add the minimum contribution from this k to the summation.
1633                 minMax[0] += minMaxK[0];
1634             } else {
1635                 // Else, ri-rj conflicts with all rk for this k, and can be swiftly eliminated.
1636                 minMax[0] = Double.NaN;
1637                 minMax[1] = Double.NaN;
1638                 return false;
1639             }
1640             if (Double.isFinite(minMaxK[1]) && Double.isFinite(minMax[1])) {
1641                 // Add the max contribution from this k to the summation.
1642                 minMax[1] += minMaxK[1];
1643             } else {
1644                 // Otherwise, the max for ri-rj is a clash.
1645                 minMax[1] = Double.NaN;
1646             }
1647         }
1648 
1649         return Double.isFinite(minMax[0]);
1650     }
1651 
1652     /**
1653      * Computes the maximum and minimum energy i,ri might have with j, and optionally (if three-body
1654      * energies in use) third residues k.
1655      *
1656      * <p>The return value should be redundant with minMax[0] being NaN.
1657      *
1658      * @param residues Array of residues under consideration.
1659      * @param minMax   Index 0 to be filled by minimum energy, index 1 filled by maximum energy.
1660      * @param i        Some residue i under consideration.
1661      * @param ri       A rotamer for residue i.
1662      * @param j        Some arbitrary residue i!=j.
1663      * @return If a valid configuration between i,ri and j could be found.
1664      */
1665     public boolean minMaxPairEnergy(Residue[] residues, double[] minMax, int i, int ri, int j) {
1666         Residue residuej = residues[j];
1667         Rotamer[] rotamersj = residuej.getRotamers();
1668         int lenrj = rotamersj.length;
1669         boolean valid = false;
1670         minMax[0] = Double.MAX_VALUE;
1671         minMax[1] = Double.MIN_VALUE;
1672 
1673         // Loop over the 2nd residues' rotamers.
1674         for (int rj = 0; rj < lenrj; rj++) {
1675             // Check for an eliminated single or eliminated pair.
1676             if (eR.check(i, ri) || eR.check(j, rj) || eR.check(i, ri, j, rj)) {
1677                 continue;
1678             }
1679 
1680             double currMax = get2Body(i, ri, j, rj);
1681             double currMin = currMax; // Will remain identical if truncating at 2-body.
1682 
1683             if (threeBodyTerm) {
1684                 double[] minMaxTriple = new double[2];
1685                 // Loop over residue k to find the min/max 3-Body energy.
1686                 boolean validPair = minMax2BodySum(residues, minMaxTriple, i, ri, j, rj);
1687                 if (!validPair) {
1688                     // Eliminate Rotamer Pair
1689                     Residue residuei = residues[i];
1690                     rO.logIfRank0(format(" Inconsistent Pair: %8s %2d, %8s %2d.",
1691                             residuei.toFormattedString(false, true), ri, residuej.toFormattedString(false, true),
1692                             rj), Level.INFO);
1693                     continue;
1694                 }
1695 
1696                 if (Double.isFinite(currMin) && Double.isFinite(minMaxTriple[0])) {
1697                     currMin += minMaxTriple[0];
1698                 } else {
1699                     currMin = Double.NaN;
1700                 }
1701 
1702                 if (Double.isFinite(currMax) && Double.isFinite(minMaxTriple[1])) {
1703                     currMax += minMaxTriple[1];
1704                 } else {
1705                     currMax = Double.NaN;
1706                 }
1707             }
1708 
1709             valid = true;
1710             if (Double.isFinite(currMin) && currMin < minMax[0]) {
1711                 minMax[0] = currMin;
1712             } // Else, we do not have a new minimum.
1713 
1714             if (Double.isFinite(currMax) && Double.isFinite(minMax[1])) {
1715                 if (currMax > minMax[1]) {
1716                     // We have a new, finite maximum.
1717                     minMax[1] = currMax;
1718                 } // Else, if currMax is finite and less than minMax[1], we do not have a new maximum.
1719             } else {
1720                 // We have a non-finite maximum.
1721                 minMax[1] = Double.NaN;
1722             }
1723         }
1724 
1725         // minMax[0] being set to NaN should be redundant with valid being false.
1726         // It would indicate i,ri clashes with something in every possible configuration.
1727         minMax[0] = (minMax[0] == Double.MAX_VALUE) ? Double.NaN : minMax[0];
1728         // minMax[1] always gets set, unless somehow everything turns up as Double.MIN_VALUE.
1729         return valid;
1730     }
1731 
1732     public void set2Body(int i, int ri, int j, int rj, double e) {
1733         set2Body(i, ri, j, rj, e, false);
1734     }
1735 
1736     /**
1737      * Store a pair energy in the pairs energy matrix.
1738      *
1739      * @param i     A residue index.
1740      * @param ri    A rotamer for residue i.
1741      * @param j     A residue index j != i.
1742      * @param rj    A rotamer for residue j.
1743      * @param e     Computed energy to store.
1744      * @param quiet Silence warnings about exceptions.
1745      */
1746     public void set2Body(int i, int ri, int j, int rj, double e, boolean quiet) {
1747         // Ensure i < j.
1748         if (j < i) {
1749             int ii = i;
1750             int iri = ri;
1751             i = j;
1752             ri = rj;
1753             j = ii;
1754             rj = iri;
1755         }
1756         try {
1757             // Find where j is in i's neighbor list (and thus the 2-body energy matrix).
1758             int[] nI = resNeighbors[i];
1759             int indJ = -1;
1760             for (int l = 0; l < nI.length; l++) {
1761                 if (nI[l] == j) {
1762                     indJ = l;
1763                     break;
1764                 }
1765             }
1766             if (indJ == -1) {
1767                 throw new IllegalArgumentException(
1768                         format(" Residue %d not found in neighbors of %d; assumed past cutoff.", j, i));
1769             } else {
1770                 twoBodyEnergy[i][ri][indJ][rj] = e;
1771             }
1772         } catch (NullPointerException npe) {
1773             if (!quiet) {
1774                 logger.info(format(" NPE for 2-body energy (%3d,%2d) (%3d,%2d).", i, ri, j, rj));
1775             }
1776             throw npe;
1777         }
1778     }
1779 
1780     /**
1781      * set3Body.
1782      *
1783      * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
1784      * @param i        a int.
1785      * @param ri       a int.
1786      * @param j        a int.
1787      * @param rj       a int.
1788      * @param k        a int.
1789      * @param rk       a int.
1790      * @param e        a double.
1791      */
1792     public void set3Body(Residue[] residues, int i, int ri, int j, int rj, int k, int rk, double e) {
1793         set3Body(residues, i, ri, j, rj, k, rk, e, false);
1794     }
1795 
1796     /**
1797      * Stores a triple energy in the triples energy matrix.
1798      *
1799      * @param i        A residue index.
1800      * @param ri       A rotamer for residue i.
1801      * @param j        A residue index j != i.
1802      * @param rj       A rotamer for residue j.
1803      * @param k        A residue index k != j, k != i.
1804      * @param rk       A rotamer for residue k.
1805      * @param e        Computed energy to store.
1806      * @param quiet    Silence warnings about exceptions.
1807      * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
1808      * @throws java.lang.IllegalStateException If threeBodyTerm is false.
1809      */
1810     public void set3Body(Residue[] residues, int i, int ri, int j, int rj, int k, int rk, double e,
1811                          boolean quiet) throws IllegalStateException {
1812         if (!threeBodyTerm) {
1813             throw new IllegalStateException(
1814                     " Attempting to set a 3-body energy when threeBodyTerm is false!");
1815         }
1816         // Ensure i < j and j < k.
1817         if (j < i) {
1818             int ii = i;
1819             int iri = ri;
1820             i = j;
1821             ri = rj;
1822             j = ii;
1823             rj = iri;
1824         }
1825         if (k < i) {
1826             int ii = i;
1827             int iri = ri;
1828             i = k;
1829             ri = rk;
1830             k = ii;
1831             rk = iri;
1832         }
1833         if (k < j) {
1834             int jj = j;
1835             int jrj = rj;
1836             j = k;
1837             rj = rk;
1838             k = jj;
1839             rk = jrj;
1840         }
1841 
1842         // Find where j is in i's neighbor list, and where k is in j's neighbor list.
1843         int[] nI = resNeighbors[i];
1844         int indJ = -1;
1845         for (int l = 0; l < nI.length; l++) {
1846             if (nI[l] == j) {
1847                 indJ = l;
1848                 break;
1849             }
1850         }
1851 
1852         int[] nJ = resNeighbors[j];
1853         int indK = -1;
1854         for (int l = 0; l < nJ.length; l++) {
1855             if (nJ[l] == k) {
1856                 indK = l;
1857                 break;
1858             }
1859         }
1860 
1861         // i,j,k: Indices in the current Residue array.
1862         // indJ, indK: Index of j in i's neighbor list, index of k in j's neighbor list.
1863         // indexI, indexJ, indexK: Indices in allResiduesList.
1864         int indexI = allResiduesList.indexOf(residues[i]);
1865         int indexJ = allResiduesList.indexOf(residues[j]);
1866         int indexK = allResiduesList.indexOf(residues[k]);
1867         if (dM.checkTriDistThreshold(indexI, ri, indexJ, rj, indexK, rk)) {
1868             throw new IllegalArgumentException(
1869                     format(" Residue %d not found in neighbors of %d; assumed past cutoff.", j, i));
1870         } else {
1871             try {
1872                 threeBodyEnergy[i][ri][indJ][rj][indK][rk] = e;
1873             } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) {
1874                 if (!quiet) {
1875                     String message = format(
1876                             " Could not access threeBodyEnergy array for 3-body energy (%3d,%2d) (%3d,%2d) (%3d,%2d)",
1877                             i, ri, j, rj, k, rk);
1878                     logger.warning(message);
1879                 }
1880                 throw ex;
1881             }
1882         }
1883     }
1884 
1885     public void setSelf(int i, int ri, double e) {
1886         setSelf(i, ri, e, false);
1887     }
1888 
1889     /**
1890      * Stores a self energy in the self energy matrix.
1891      *
1892      * @param i     A residue index.
1893      * @param ri    A rotamer for residue i.
1894      * @param e     Computed energy to store.
1895      * @param quiet Silence warnings about exceptions.
1896      */
1897     public void setSelf(int i, int ri, double e, boolean quiet) {
1898         try {
1899             selfEnergy[i][ri] = e;
1900         } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) {
1901             if (!quiet) {
1902                 logger.warning(format(" NPE or array index error for (%3d,%2d)", i, ri));
1903             }
1904             throw ex;
1905         }
1906     }
1907 
1908     public void turnOffAllResidues(Residue[] residues) {
1909         if (residues == null) {
1910             return;
1911         }
1912         for (Residue residue : residues) {
1913             turnOffResidue(residue);
1914         }
1915     }
1916 
1917     public void turnOffResidue(Residue residue) {
1918         turnOffAtoms(residue);
1919         applyDefaultRotamer(residue);
1920     }
1921 
1922     public void turnOnAllResidues(Residue[] residues) {
1923         if (residues == null) {
1924             return;
1925         }
1926         for (Residue residue : residues) {
1927             turnOnAtoms(residue);
1928         }
1929     }
1930 
1931     public void turnOnResidue(Residue residue, int ri) {
1932         turnOnAtoms(residue);
1933         Rotamer[] rotamers = residue.getRotamers();
1934         applyRotamer(residue, rotamers[ri]);
1935     }
1936 
1937     /**
1938      * Find the min/max of the 2-body energy.
1939      *
1940      * @param residues The residue array.
1941      * @param minMax   The bound on the 3-body energy (minMax[0] = min, minMax[1] = max.
1942      * @param i        Residue i
1943      * @param ri       Rotamer ri of Residue i
1944      * @param j        Residue j
1945      * @param rj       Rotamer rj of Residue j
1946      * @return true if this term is valid.
1947      */
1948     private boolean minMax2BodySum(Residue[] residues, double[] minMax, int i, int ri, int j, int rj) {
1949         int nres = residues.length;
1950         double minSum = 0.0;
1951         double maxSum = 0.0;
1952         for (int k = 0; k < nres; k++) {
1953             if (k == i || k == j) {
1954                 continue;
1955             }
1956             Residue residuek = residues[k];
1957             Rotamer[] romatersk = residuek.getRotamers();
1958             int lenrk = romatersk.length;
1959             double currentMin = Double.MAX_VALUE;
1960             double currentMax = Double.MIN_VALUE;
1961             for (int rk = 0; rk < lenrk; rk++) {
1962                 if (eR.check(k, rk)) {
1963                     // k,rk is part of no valid phase space, so ignore it.
1964                     continue;
1965                 }
1966                 if (eR.check(i, ri, k, rk) || eR.check(j, rj, k, rk)) {
1967                     // Not implemented: check(i, ri, j, rj, k, rk).
1968                     // k,rk conflicts with i,ri or j,rj, so the max is now Double.NaN. No effect on minimum.
1969                     currentMax = Double.NaN;
1970                 } else {
1971                     double current = get3Body(residues, i, ri, j, rj, k, rk);
1972                     if (Double.isFinite(current) && current < currentMin) {
1973                         currentMin = current;
1974                     } // Else, no new minimum found.
1975                     if (Double.isFinite(current) && Double.isFinite(currentMax)) {
1976                         if (current > currentMax) {
1977                             currentMax = current;
1978                         } // Else, we have failed to find a new finite maximum.
1979                     } else {
1980                         // The maximum is NaN.
1981                         currentMax = Double.NaN;
1982                     }
1983                 }
1984             }
1985             if (currentMin == Double.MAX_VALUE || !Double.isFinite(minSum)) {
1986                 // We have failed to find a viable configuration for i,ri,j,rj, as it conflicts with all rk
1987                 // for this k.
1988                 minMax[0] = Double.NaN;
1989                 minMax[1] = Double.NaN;
1990                 return false;
1991             } else {
1992                 // Add finite current min to minSum.
1993                 minSum += currentMin;
1994             }
1995             if (Double.isFinite(maxSum) && Double.isFinite(currentMax)) {
1996                 maxSum += currentMax;
1997             } else {
1998                 maxSum = Double.NaN;
1999             }
2000         }
2001         minMax[0] = minSum;
2002         minMax[1] = maxSum;
2003         return true;
2004     }
2005 
2006     /**
2007      * Calculates the minimum and maximum summations over additional residues for some rotamer triples
2008      * ri-rj-rk.
2009      *
2010      * @param residues Residues under consideration.
2011      * @param minMax   Result array: 0 is min summation, 1 max summation.
2012      * @param i        Residue i.
2013      * @param ri       Rotamer for residue i.
2014      * @param j        Residue j!=i.
2015      * @param rj       Rotamer for residue j.
2016      * @param k        Residue k!=j and k!=i.
2017      * @param rk       Rotamer for residue k.
2018      * @return False if ri-rj-rk always clashes with other residues.
2019      * @throws IllegalArgumentException if there are pre-existing eliminations in ri-rj-rk.
2020      */
2021     private boolean minMaxE3(Residue[] residues, double[] minMax, int i, int ri, int j, int rj, int k,
2022                              int rk) throws IllegalArgumentException {
2023         Residue resi = residues[i];
2024         Residue resj = residues[j];
2025         Residue resk = residues[k];
2026         if (eR.check(i, ri) || eR.check(j, rj) || eR.check(k, rk) || eR.check(i, ri, j, rj)
2027                 || eR.check(i, ri, k, rk) || eR.check(j, rj, k, rk)) {
2028             // Not implemented: check(i, ri, j, rj, k, rk).
2029             throw new IllegalArgumentException(
2030                     format(" Called for minMaxE2 on an eliminated triple %s-%d %s-%d %s-%d",
2031                             resi.toFormattedString(false, true), ri, resj.toFormattedString(false, true), rj,
2032                             resk.toFormattedString(false, true), rk));
2033         }
2034 
2035         // These two are a summation of mins/maxes over all fourth residues l.
2036         minMax[0] = 0;
2037         minMax[1] = 0;
2038         int nRes = residues.length;
2039         for (int l = 0; l < nRes; l++) {
2040             if (l == i || l == j || l == k) {
2041                 continue;
2042             }
2043             Residue resl = residues[l];
2044             Rotamer[] rotsl = resl.getRotamers();
2045             int lenrl = rotsl.length;
2046 
2047             // Find min/max rl for residue l.
2048             double currentMax = Double.MIN_VALUE;
2049             double currentMin = Double.MAX_VALUE;
2050 
2051             for (int rl = 0; rl < lenrl; rl++) {
2052                 if (eR.check(l, rl) || eR.check(k, rk, l, rl)) {
2053                     // Not valid phase space for anything.
2054                     continue;
2055                 }
2056 
2057                 double current;
2058                 if (eR.check(i, ri, l, rl) || eR.check(j, rj, l, rl)) {
2059                     // Not implemented: checking ri-rj-rl, ri-rk-rl, rj-rk-rl, or ri-rj-rk-rl.
2060                     current = Double.NaN;
2061                 } else {
2062                     // ri-rj-rl is accounted for at a different part of the summation as ri-rj-rk.
2063                     current =
2064                             get3Body(residues, i, ri, k, rk, l, rl) + get3Body(residues, j, rj, k, rk, l, rl);
2065                 }
2066 
2067                 // TODO: Add quads to the DEE summation.
2068                 // Would have to replace "current" with array "currentQuads".
2069                 // double[] minMaxQuads;
2070                 // minMaxE4(args)
2071                 if (Double.isFinite(current) && current < currentMin) {
2072                     // rl forms a more favorable 3-body than any prior rl for this residue l.
2073                     currentMin = current;
2074                 }
2075 
2076                 if (Double.isFinite(current) && Double.isFinite(currentMax)) {
2077                     if (current > currentMax) {
2078                         currentMax = current;
2079                     } // Else, no new finite max found.
2080                 } else {
2081                     currentMax = Double.NaN;
2082                 }
2083             }
2084 
2085             if (Double.isFinite(currentMin)) {
2086                 minMax[0] += currentMin;
2087             } else {
2088                 // Else, ri-rj-rk inevitably conflicts with l.
2089                 minMax[0] = Double.NaN;
2090                 minMax[1] = Double.NaN;
2091                 return false;
2092             }
2093 
2094             if (Double.isFinite(currentMax) && Double.isFinite(minMax[1])) {
2095                 minMax[1] += currentMax;
2096             } else {
2097                 minMax[1] = Double.NaN;
2098             }
2099             // Finished with this residue l.
2100         }
2101         return Double.isFinite(minMax[0]);
2102     }
2103 
2104     /**
2105      * Applies the "default" rotamer: currently the 0'th rotamer.
2106      *
2107      * @param residue Residue to apply a default rotamer for.
2108      */
2109     private void applyDefaultRotamer(Residue residue) {
2110         applyRotamer(residue, residue.getRotamers()[0]);
2111     }
2112 
2113     private int nameToNumber(String residueString, Residue[] residues) throws NumberFormatException {
2114         int ret = -1;
2115         for (int x = 0; x < residues.length; x++) {
2116             if (residueString.equals(residues[x].toString())) {
2117                 ret = x;
2118                 break;
2119             }
2120         }
2121         if (ret == -1) {
2122             throw new NumberFormatException();
2123         }
2124         return ret;
2125     }
2126 
2127     private void condenseEnergyMap(Map<Integer, Integer[]> energyMap) {
2128         Set<Integer> keys = energyMap.keySet();
2129         HashMap<Integer, Integer[]> tempMap = new HashMap<>();
2130         int count = 0;
2131         for (int key : keys) {
2132             tempMap.put(count, energyMap.get(key));
2133             count++;
2134         }
2135         energyMap.clear();
2136         energyMap.putAll(tempMap);
2137     }
2138 }