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;
39  
40  import edu.rit.pj.Comm;
41  import edu.rit.pj.ParallelTeam;
42  import edu.rit.pj.WorkerTeam;
43  import ffx.algorithms.AlgorithmListener;
44  import ffx.algorithms.Terminatable;
45  import ffx.algorithms.mc.MCMove;
46  import ffx.algorithms.optimize.manybody.DistanceMatrix;
47  import ffx.algorithms.optimize.manybody.EliminatedRotamers;
48  import ffx.algorithms.optimize.manybody.EnergyExpansion;
49  import ffx.algorithms.optimize.manybody.EnergyRegion;
50  import ffx.algorithms.optimize.manybody.FourBodyEnergyRegion;
51  import ffx.algorithms.optimize.manybody.GoldsteinPairRegion;
52  import ffx.algorithms.optimize.manybody.ManyBodyCell;
53  import ffx.algorithms.optimize.manybody.RotamerMatrixMC;
54  import ffx.algorithms.optimize.manybody.RotamerMatrixMove;
55  import ffx.algorithms.optimize.manybody.SelfEnergyRegion;
56  import ffx.algorithms.optimize.manybody.ThreeBodyEnergyRegion;
57  import ffx.algorithms.optimize.manybody.TwoBodyEnergyRegion;
58  import ffx.crystal.Crystal;
59  import ffx.crystal.SymOp;
60  import ffx.numerics.Potential;
61  import ffx.potential.DualTopologyEnergy;
62  import ffx.potential.ForceFieldEnergy;
63  import ffx.potential.MolecularAssembly;
64  import ffx.potential.QuadTopologyEnergy;
65  import ffx.potential.bonded.Atom;
66  import ffx.potential.bonded.Polymer;
67  import ffx.potential.bonded.Residue;
68  import ffx.potential.bonded.ResidueState;
69  import ffx.potential.bonded.Rotamer;
70  import ffx.potential.bonded.RotamerLibrary;
71  import ffx.potential.nonbonded.NonbondedCutoff;
72  import ffx.potential.nonbonded.VanDerWaals;
73  import ffx.potential.openmm.OpenMMEnergy;
74  import ffx.potential.parameters.TitrationUtils;
75  import ffx.potential.parsers.PDBFilter;
76  import ffx.utilities.Constants;
77  import ffx.utilities.ObjectPair;
78  import ffx.utilities.Resources;
79  import org.apache.commons.configuration2.CompositeConfiguration;
80  import org.apache.commons.io.FilenameUtils;
81  
82  import java.io.BufferedWriter;
83  import java.io.File;
84  import java.io.FileWriter;
85  import java.io.IOException;
86  import java.nio.file.Path;
87  import java.nio.file.Paths;
88  import java.util.ArrayList;
89  import java.util.Arrays;
90  import java.util.Collections;
91  import java.util.Comparator;
92  import java.util.HashSet;
93  import java.util.List;
94  import java.util.Objects;
95  import java.util.Random;
96  import java.util.Set;
97  import java.util.function.BiFunction;
98  import java.util.function.ToDoubleFunction;
99  import java.util.logging.Level;
100 import java.util.logging.Logger;
101 import java.util.stream.Collectors;
102 import java.util.stream.IntStream;
103 
104 import static ffx.potential.bonded.Residue.ResidueType.NA;
105 import static ffx.potential.bonded.RotamerLibrary.applyRotamer;
106 import static java.lang.Boolean.parseBoolean;
107 import static java.lang.Double.parseDouble;
108 import static java.lang.Integer.parseInt;
109 import static java.lang.String.format;
110 import static java.lang.System.arraycopy;
111 import static java.util.Arrays.copyOf;
112 import static org.apache.commons.math3.util.FastMath.abs;
113 import static org.apache.commons.math3.util.FastMath.log;
114 
115 /**
116  * Optimize protein side-chain conformations and nucleic acid backbone conformations using rotamers.
117  *
118  * @author Michael J. Schnieders
119  * @author Jacob M. Litman
120  * @author Stephen D. LuCore
121  * @author Mallory R. Tollefson
122  * @since 1.0
123  */
124 public class RotamerOptimization implements Terminatable {
125 
126     /**
127      * Logger for this class.
128      */
129     private static final Logger logger = Logger.getLogger(RotamerOptimization.class.getName());
130     /**
131      * Fallback if there is no vdW node.
132      */
133     private static final double FALLBACK_TWO_BODY_CUTOFF = 0;
134     /**
135      * MolecularAssembly to perform rotamer optimization on.
136      */
137     protected MolecularAssembly molecularAssembly;
138     /**
139      * The Potential to evaluate during rotamer optimization.
140      */
141     protected final Potential potential;
142     /**
143      * AlgorithmListener who should receive updates as the optimization runs.
144      */
145     protected final AlgorithmListener algorithmListener;
146     /**
147      * World Parallel Java communicator.
148      */
149     private final Comm world;
150     /**
151      * Number of Parallel Java processes.
152      */
153     private final int numProc;
154     /**
155      * Rank of this process.
156      */
157     private final int rank;
158     /**
159      * Flag to indicate if this is the master process.
160      */
161     protected final boolean rank0;
162     /**
163      * Flag to control the verbosity of printing.
164      */
165     private final boolean print = false;
166     /**
167      * Flag to calculate and print additional energies (mostly for debugging).
168      */
169     private final boolean verboseEnergies = true;
170 
171     /**
172      * If true, write out an energy restart file.
173      */
174     protected boolean writeEnergyRestart = true;
175     /**
176      * Parameters for box optimization are stored here.
177      */
178     BoxOptimization boxOpt;
179     /**
180      * Represents the method called to obtain the directory corresponding to the current energy; will
181      * be a simple return null for potential energy evaluations. While current energy calls will fill
182      * the rotamer list with the current rotamers of the residue, other methods may skip applying the
183      * rotamer directly.
184      */
185     private final BiFunction<List<Residue>, List<Rotamer>, File> dirSupplier;
186     /**
187      * Represents the method called to obtain energy for the current rotamer or state; defaults to the
188      * existing potential energy code. May discard the input file.
189      */
190     private final ToDoubleFunction<File> eFunction;
191     /**
192      * Flag to indicate verbose logging.
193      */
194     private final boolean verbose;
195     /**
196      * The DistanceMatrix class handles calculating distances between residues.
197      */
198     private DistanceMatrix dM;
199     /**
200      * The EnergyExpansion class compute terms in the many-body energy expansion.
201      */
202     private EnergyExpansion eE;
203     /**
204      * The EliminatedRotamers class tracks eliminated rotamers and rotamer paris.
205      */
206     private EliminatedRotamers eR;
207     /**
208      * RotamerLibrary instance.
209      */
210     protected RotamerLibrary library = RotamerLibrary.getDefaultLibrary();
211     /**
212      * Parallel evaluation of quantities used during Goldstein Pair elimination.
213      */
214     private GoldsteinPairRegion goldsteinPairRegion;
215     /**
216      * Parallel evaluation of many-body energy sums.
217      */
218     private EnergyRegion energyRegion;
219     /**
220      * Flag to indicate a request to terminate the optimization.
221      */
222     private boolean terminate = false;
223     /**
224      * Flag to indicate if the algorithm is running (done == false) or completed (done == true).
225      */
226     private boolean done = true;
227     /**
228      * Two-Body cutoff distance.
229      */
230     private double twoBodyCutoffDist;
231     /**
232      * Flag to control use of 3-body terms.
233      */
234     private boolean threeBodyTerm = false;
235     /**
236      * Three-body cutoff distance.
237      */
238     private double threeBodyCutoffDist;
239     /**
240      * Interaction partners of a Residue that come after it.
241      */
242     private int[][] resNeighbors;
243     /**
244      * All interaction partners of a Residue, including prior residues.
245      */
246     private int[][] bidiResNeighbors;
247     /**
248      * Flag to prune clashes.
249      */
250     private boolean pruneClashes = true;
251     /**
252      * Flag to prune pair clashes.
253      */
254     private boolean prunePairClashes = true;
255     /**
256      * Number of permutations whose energy is explicitly evaluated.
257      */
258     private int evaluatedPermutations = 0;
259     /**
260      * Permutations are printed when modulo this field is zero.
261      */
262     private int evaluatedPermutationsPrint = 0;
263     /**
264      * Total boltzmann calculated during the partition function
265      */
266     private double totalBoltzmann = 0;
267     /**
268      * Reference energy for calculating boltzmanns in the partition function
269      */
270     private double refEnergy = 0;
271     /**
272      * Rotamer populations from the partition function
273      */
274     private double[][] fraction;
275     /**
276      * Botlzmann weights of every rotamer
277      */
278     private double[][] populationBoltzmann;
279     /**
280      * pH during titration rotamer optimization
281      */
282     private double pH;
283     /**
284      * Energy restraint on titration
285      */
286     private double pHRestraint = 0;
287     /**
288      * Recompute the self energies from restart when changing the pH
289      */
290     private boolean recomputeSelf = false;
291     /**
292      * True when running the GenZ algorithm
293      */
294     boolean genZ = false;
295     /**
296      * List of residues to optimize; they may not be contiguous or all members of the same chain.
297      */
298     private List<Residue> residueList;
299     /**
300      * This is the optimal rotamers corresponding to residueList.
301      */
302     protected int[] optimum;
303 
304   /**
305    * Size of the sliding window.
306    */
307   private int windowSize = 7;
308   /**
309    * The distance the sliding window moves.
310    */
311   private int increment = 3;
312   /**
313    * In sliding window, whether to revert an unfavorable change.
314    */
315   protected boolean revert;
316   /**
317    * The sliding window direction.
318    */
319   protected Direction direction = Direction.FORWARD;
320 
321   /**
322    * The distance that the distance matrix checks for.
323    */
324   private double distance = 2.0;
325   /**
326    * Default distance method is to find the shortest distance between residues.
327    */
328   private DistanceMethod distanceMethod = DistanceMethod.RESIDUE;
329   /**
330    * The algorithm to use for rotamer optimization.
331    */
332   private Algorithm algorithm = null;
333 
334   /**
335    * Flag to indicate use of the Goldstein criteria instead of the less stringent Dead-End
336    * Elimination criteria.
337    */
338   private boolean useGoldstein = true;
339   /**
340    * The number of most-favorable structures to include as output.
341    */
342   private int ensembleNumber = 1;
343   /**
344    * The energy buffer applied to each elimination criteria to affect an ensemble.
345    */
346   private double ensembleBuffer = 0.0;
347   /**
348    * The step value of the energy buffer for use with ensemble search.
349    */
350   private double ensembleBufferStep = 0.5;
351   /**
352    * The energy boundary for structures to be included in the final ensemble.
353    */
354   private double ensembleEnergy = 0.0;
355   /**
356    * File to contain ensemble of structures.
357    */
358   private File ensembleFile;
359   /**
360    * PDBFilter to write out ensemble snapshots.
361    */
362   private PDBFilter ensembleFilter;
363   /**
364    * Clash energy threshold (kcal/mole).
365    */
366   private double clashThreshold = 25.0;
367   /**
368    * Clash energy threshold (kcal/mol) for MultiResidues, which can have much more variation in self
369    * and 2-Body energies.
370    */
371   private double multiResClashThreshold = 80.0;
372   /**
373    * Clash energy threshold (kcal/mole).
374    */
375   private double pairClashThreshold = 25.0;
376   /**
377    * Pair clash energy threshold (kcal/mol) for MultiResidues.
378    */
379   private double multiResPairClashAddn = 80.0;
380   /**
381    * An array of atomic coordinates (length 3 * the number of atoms).
382    */
383   private double[] x = null;
384   /**
385    * A flag to indicate use of the full N-Body AMOEBA potential energy during the rotamer
386    * optimization.
387    */
388   private boolean useForceFieldEnergy = false;
389   /**
390    * Threshold to eliminate nucleic acid Rotamers based on excessive correction distances; 0
391    * indicates the threshold is not being implemented.
392    */
393   private double nucleicCorrectionThreshold = 0;
394   /**
395    * The approximate energy from summing the backbone energy, self-energies, pair-energies, etc.
396    */
397   private double approximateEnergy = 0;
398   /**
399    * Minimum number of nucleic acid Rotamers to use for rotamer optimization should some be
400    * eliminated by the nucleic correction threshold.
401    */
402   private int minNumberAcceptedNARotamers = 10;
403   /**
404    * Factor by which to multiply the pruning constraints for nucleic acids.
405    */
406   private double nucleicPruningFactor = 10.0;
407   /**
408    * The arithmetic mean of 1.0 and the pruning factor, and is applied for AA-NA pairs.
409    */
410   private double nucleicPairsPruningFactor = ((1.0 + nucleicPruningFactor) / 2);
411   /**
412    * A list of all residues in the system, which is used to compute a distance matrix.
413    */
414   private List<Residue> allResiduesList = null;
415   /**
416    * An array of all residues in the system, which is used to compute a distance matrix.
417    */
418   private Residue[] allResiduesArray = null;
419   /**
420    * Number of residues being optimized.
421    */
422   private int nAllResidues = 0;
423   /**
424    * The array of optimum rotamers for the subset of residues being optimized during box or window
425    * optimization.
426    */
427   protected int[] optimumSubset;
428   /**
429    * If true, load an energy restart file.
430    */
431   protected boolean loadEnergyRestart = false;
432   /**
433    * Energy restart File instance.
434    */
435   private File energyRestartFile;
436   /**
437    * ParallelTeam instance.
438    */
439   private ParallelTeam parallelTeam;
440   /**
441    * Flag to indicate computation of 4-body energy values. This is limited to the study 4-body energy
442    * magnitudes, but is not included in the rotamer optimization.
443    */
444   private boolean compute4BodyEnergy = false;
445   /**
446    * Flag to indicate use of box optimization.
447    */
448   protected boolean usingBoxOptimization = false;
449   /**
450    * If a pair of residues have two atoms closer together than the superposition threshold, the
451    * energy is set to NaN.
452    */
453   private double superpositionThreshold = 0.25;
454   /**
455    * Flag to indicate computation of a many-body expansion for original coordinates.
456    */
457   private boolean decomposeOriginal = false;
458   /**
459    * Flag to indicate use of MC optimization.
460    */
461   private boolean monteCarlo = false;
462   /**
463    * Number of MC optimization steps.
464    */
465   private int nMCSteps = 1000000;
466   /**
467    * MC temperature (K).
468    */
469   private double mcTemp = 298.15;
470   /**
471    * Check to see if proposed move has an eliminated 2-body or higher-order term.
472    */
473   private boolean mcUseAll = false;
474   /**
475    * Skips brute force enumeration in favor of pure Monte Carlo. Recommended only for testing.
476    */
477   private boolean mcNoEnum = false;
478   /**
479    * Sets whether files should be printed; true for standalone applications, false for some
480    * applications which use rotamer optimization as part of a larger process.
481    */
482   protected boolean printFiles = true;
483   /**
484    * Stores states of each ensemble if printFiles is false.
485    */
486   private List<ObjectPair<ResidueState[], Double>> ensembleStates;
487   /**
488    * Maximum depth to check if a rotamer can be eliminated.
489    */
490   private int maxRotCheckDepth;
491   /**
492    * Writes energies to restart file.
493    */
494   protected BufferedWriter energyWriter;
495 
496   /**
497    * False unless JUnit testing.
498    */
499   private boolean testing = false;
500   /**
501    * False unless ManyBodyTest is occurring.
502    */
503   private boolean monteCarloTesting = false;
504   /**
505    * Test Self-Energy Elimination.
506    */
507   private boolean testSelfEnergyEliminations = false;
508   /**
509    * Test Pair-Energy Elimination.
510    *
511    * <p>If greater than or equal to 0, test the specified residue.
512    */
513   private int testPairEnergyEliminations = -1;
514   /**
515    * Test Triple-Energy Elimination.
516    *
517    * <p>If greater than or equal to 0, test the specified residues.
518    */
519   private int testTripleEnergyEliminations1 = -1;
520   /**
521    * Test Triple-Energy Elimination.
522    *
523    * <p>If greater than or equal to 0, test the specified residues.
524    */
525   private int testTripleEnergyEliminations2 = -1;
526   /**
527    * Only for unit testing; turns off the singles elimination criterion.
528    */
529   private boolean selfEliminationOn = true;
530   /**
531    * Only for unit testing; turns off the pairs elimination criterion.
532    */
533   private boolean pairEliminationOn = true;
534 
535   /**
536    * RotamerOptimization constructor.
537    *
538    * @param molecularAssembly The MolecularAssembly to search rotamers for.
539    * @param potential         a {@link ffx.numerics.Potential} object.
540    * @param algorithmListener AlgorithmListener to update the GUI.
541    */
542   public RotamerOptimization(MolecularAssembly molecularAssembly, Potential potential,
543                              AlgorithmListener algorithmListener) {
544 
545     this.molecularAssembly = molecularAssembly;
546     this.potential = potential;
547     if (potential instanceof ForceFieldEnergy) {
548       ((ForceFieldEnergy) potential).setPrintOnFailure(false, true);
549     } else if (potential instanceof DualTopologyEnergy) {
550       ((DualTopologyEnergy) potential).setPrintOnFailure(false, true);
551     } else if (potential instanceof QuadTopologyEnergy) {
552       ((QuadTopologyEnergy) potential).setPrintOnFailure(false, true);
553     }
554     this.algorithmListener = algorithmListener;
555 
556     eFunction = this::currentPE;
557     dirSupplier = (List<Residue> resList, List<Rotamer> rotList) -> null;
558 
559     world = Comm.world();
560     numProc = world.size();
561     rank = world.rank();
562 
563     rank0 = rank == 0;
564 
565     boxOpt = new BoxOptimization(this);
566 
567     CompositeConfiguration properties = molecularAssembly.getProperties();
568     verbose = properties.getBoolean("verbose", false);
569 
570     // Set the default 2-body Cutoff to the van der Waals cutoff.
571     ForceFieldEnergy forceFieldEnergy = molecularAssembly.getPotentialEnergy();
572     VanDerWaals vdW = forceFieldEnergy.getVdwNode();
573     if (vdW != null) {
574       NonbondedCutoff nonBondedCutoff = vdW.getNonbondedCutoff();
575       twoBodyCutoffDist = nonBondedCutoff.off;
576     } else {
577       twoBodyCutoffDist = FALLBACK_TWO_BODY_CUTOFF;
578     }
579 
580     // Process properties; most are seldom used and not available in ManyBodyOptions.
581     // Handle testing flags.
582     boolean testing = properties.getBoolean("manybody-testing", false);
583     if (testing) {
584       turnRotamerSingleEliminationOff();
585       turnRotamerPairEliminationOff();
586     }
587     boolean monteCarloTesting = properties.getBoolean("manybody-testing-mc", false);
588     if (monteCarloTesting) {
589       setMonteCarloTesting(true);
590     }
591 
592     // Compute 4-body energies.
593     String computeQuads = properties.getString("ro-compute4BodyEnergy");
594     if (computeQuads != null) {
595       this.compute4BodyEnergy = parseBoolean(computeQuads);
596       logger.info(format(" (KEY) compute4BodyEnergy: %b", this.compute4BodyEnergy));
597     }
598 
599     // Box / sliding window options.
600     String direction = properties.getString("ro-direction");
601     String boxDimensions = properties.getString("ro-boxDimensions");
602     if (direction != null) {
603       this.direction = Direction.valueOf(direction);
604       logger.info(format(" (KEY) direction: %s", this.direction));
605     }
606     if (boxDimensions != null) {
607       boxOpt.update(boxDimensions);
608     }
609 
610     // Ensemble options.
611     String ensembleNumber = properties.getString("ro-ensembleNumber");
612     String ensembleEnergy = properties.getString("ro-ensembleEnergy");
613     String ensembleBuffer = properties.getString("ro-ensembleBuffer");
614     if (ensembleNumber != null) {
615       this.ensembleNumber = parseInt(ensembleNumber);
616       this.ensembleBuffer = 5.0;
617       this.ensembleBufferStep = 0.1 * this.ensembleBuffer;
618       logger.info(format(" (KEY) ensembleNumber: %d", this.ensembleNumber));
619     }
620     if (ensembleBuffer != null) {
621       this.ensembleBuffer = parseDouble(ensembleBuffer);
622       this.ensembleBufferStep = 0.1 * this.ensembleBuffer;
623       logger.info(format(" (KEY) ensembleBuffer: %.2f", this.ensembleBuffer));
624     }
625     if (ensembleEnergy != null) {
626       this.ensembleEnergy = parseDouble(ensembleEnergy);
627       logger.info(format(" (KEY) ensembleEnergy: %.2f", this.ensembleEnergy));
628     }
629 
630     // Nucleic Acid Options.
631     String nucleicPruningFactor = properties.getString("ro-nucleicPruningFactor");
632     String nucleicCorrectionThreshold = properties.getString("ro-nucleicCorrectionThreshold");
633     String minimumNumberAcceptedNARotamers = properties.getString(
634         "ro-minimumNumberAcceptedNARotamers");
635     if (nucleicPruningFactor != null) {
636       double value = parseDouble(nucleicPruningFactor);
637       this.nucleicPruningFactor = (value >= 0 ? value : 1.0);
638       this.nucleicPairsPruningFactor = (1.0 + value) / 2;
639       logger.info(format(" (KEY) nucleicPruningFactor: %.2f", this.nucleicPruningFactor));
640     }
641     if (nucleicCorrectionThreshold != null) {
642       double value = parseDouble(nucleicCorrectionThreshold);
643       this.nucleicCorrectionThreshold = (value >= 0 ? value : 0);
644       logger.info(
645           format(" (KEY) nucleicCorrectionThreshold: %.2f", this.nucleicCorrectionThreshold));
646     }
647     if (minimumNumberAcceptedNARotamers != null) {
648       int value = parseInt(minimumNumberAcceptedNARotamers);
649       this.minNumberAcceptedNARotamers = (value > 0 ? value : 10);
650       logger.info(
651           format(" (KEY) minimumNumberAcceptedNARotamers: %d", this.minNumberAcceptedNARotamers));
652     }
653 
654     // Superposition
655     String superpositionThreshold = properties.getString("ro-superpositionThreshold");
656     if (superpositionThreshold != null) {
657       this.superpositionThreshold = parseDouble(superpositionThreshold);
658       logger.info(format(" (KEY) superpositionThreshold: %.2f", this.superpositionThreshold));
659     }
660 
661     // Multi-residue clash thresholds
662     String multiResClashThreshold = properties.getString("ro-multiResClashThreshold");
663     String multiResPairClashAddition = properties.getString("ro-multiResPairClashAddition");
664     if (multiResClashThreshold != null) {
665       this.multiResClashThreshold = parseDouble(multiResClashThreshold);
666       logger.info(format(" (KEY) multiResClashThreshold: %.2f", this.multiResClashThreshold));
667     }
668     if (multiResPairClashAddition != null) {
669       this.multiResPairClashAddn = parseDouble(multiResPairClashAddition);
670       logger.info(format(" (KEY) multiResPairClashAddition: %.2f", this.multiResPairClashAddn));
671     }
672 
673     // Monte Carlo Options.
674     String mcTemp = properties.getString("ro-mcTemp");
675     String mcUseAll = properties.getString("ro-mcUseAll");
676     String mcNoEnum = properties.getString("ro-debug-mcNoEnum");
677     if (mcTemp != null) {
678       this.mcTemp = parseDouble(mcTemp);
679       logIfRank0(format(" (KEY) mcTemp: %10.6f", this.mcTemp));
680     }
681     if (mcUseAll != null) {
682       this.mcUseAll = parseBoolean(mcUseAll);
683       logIfRank0(format(" (KEY) mcUseAll: %b", this.mcUseAll));
684     }
685     if (mcNoEnum != null) {
686       this.mcNoEnum = parseBoolean(mcNoEnum);
687       logIfRank0(format(" (KEY) debug-mcNoEnum: %b", this.mcNoEnum));
688     }
689 
690     String propStr = properties.getString("ro-maxRotCheckDepth");
691     int defaultMaxRotCheckDepth = 1;
692     if (propStr != null) {
693       try {
694         maxRotCheckDepth = parseInt(propStr);
695         if (maxRotCheckDepth > 3 || maxRotCheckDepth < 0) {
696           throw new IllegalArgumentException(" ro-maxRotCheckDepth must be between 0-3 inclusive!");
697         }
698       } catch (Exception ex) {
699         maxRotCheckDepth = defaultMaxRotCheckDepth;
700         logger.warning(format(" Could not parse %s value %s as valid integer; defaulting to %d",
701             "ro-maxRotCheckDepth", propStr, maxRotCheckDepth));
702         logger.warning(format(" Exception: %s", ex));
703       }
704     } else {
705       maxRotCheckDepth = defaultMaxRotCheckDepth;
706     }
707 
708     setUpRestart();
709   }
710 
711   /**
712    * Set the writeEnergyRestart flag.
713    *
714    * @param writeEnergyRestart If true, write out an energy restart file.
715    */
716   public void setWriteEnergyRestart(boolean writeEnergyRestart) {
717     this.writeEnergyRestart = writeEnergyRestart;
718   }
719 
720   /**
721    * Set the "use" flag to true for all variable atoms in a residue.
722    *
723    * @param residue The residue to turn off.
724    */
725   private static void turnOffAtoms(Residue residue) {
726     switch (residue.getResidueType()) {
727       case NA:
728       case AA:
729         List<Atom> atomList = residue.getVariableAtoms();
730         for (Atom atom : atomList) {
731           atom.setUse(false);
732         }
733         break;
734       default:
735         atomList = residue.getAtomList();
736         for (Atom atom : atomList) {
737           atom.setUse(false);
738         }
739         break;
740     }
741   }
742 
743   /**
744    * Set the "use" flag to true for all variable atoms in a residue.
745    *
746    * @param residue The Residue to turn on.
747    */
748   private static void turnOnAtoms(Residue residue) {
749     switch (residue.getResidueType()) {
750       case NA:
751       case AA:
752         List<Atom> atomList = residue.getVariableAtoms();
753         for (Atom atom : atomList) {
754           atom.setUse(true);
755         }
756         break;
757       default:
758         atomList = residue.getAtomList();
759         for (Atom atom : atomList) {
760           atom.setUse(true);
761         }
762         break;
763     }
764   }
765 
766   /**
767    * Checks if residue i is considered to be interacting with residue j, and thus has non-null
768    * elements in the pair energies matrix.
769    *
770    * @param i A residue index.
771    * @param j A residue index j != i.
772    * @return If i and j interact.
773    */
774   public boolean checkNeighboringPair(int i, int j) {
775     assert i != j;
776     final int first;
777     final int second;
778     if (i > j) {
779       first = j;
780       second = i;
781     } else {
782       first = i;
783       second = j;
784     }
785     return Arrays.stream(resNeighbors[first]).anyMatch(l -> l == second);
786   }
787 
788   /**
789    * Checks if residue i is considered to be interacting with residue j, that residue k is
790    * interacting with either i or j, and thus i-j-k has non-null elements in the triple energy
791    * matrix.
792    *
793    * @param i A residue index.
794    * @param j A residue index j != i.
795    * @param k A residue index k != i, k != j.
796    * @return If i, j, and k form an interacting triple.
797    */
798   public boolean checkNeighboringTriple(int i, int j, int k) {
799     assert i != j && i != k && j != k;
800     int[] vals = new int[]{i, j, k};
801     Arrays.sort(vals);
802     final int first = vals[0];
803     final int second = vals[1];
804     final int third = vals[2];
805     if (!checkNeighboringPair(first, second)) {
806       return false;
807     }
808     return (checkNeighboringPair(first, third) || checkNeighboringPair(second, third));
809   }
810 
811   /**
812    * Checks the pair elimination array to see if this permutation has been eliminated.
813    *
814    * @param i           Residue number
815    * @param ri          Rotamer number
816    * @param currentRots Array of current rotamers indeces.
817    * @return If valid
818    */
819   public boolean checkValidMove(int i, int ri, int[] currentRots) {
820     int nRes = currentRots.length;
821     for (int j = 0; j < nRes; j++) {
822       if (j == i) {
823         continue;
824       }
825       if (eR.check(j, currentRots[j], i, ri)) {
826         return false;
827       }
828     }
829     return true;
830   }
831 
832   /**
833    * Computes the environment/backbone energy, defined as energy with all sidechains under
834    * consideration turned off in their 0th rotamer.
835    *
836    * @param residues Residues under optimization.
837    * @return Backbone energy Eenv/bb.
838    * @throws ArithmeticException If an exception in calculating energy is found.
839    */
840   public double computeBackboneEnergy(Residue[] residues) throws ArithmeticException {
841     // Set all atoms to be used.
842     Atom[] atoms = molecularAssembly.getAtomArray();
843     for (Atom atom : atoms) {
844       atom.setUse(true);
845     }
846 
847     // Turn off all Residues and set them to their default conformation.
848     turnOffAllResidues(residues);
849 
850     // Compute and return the backbone energy.
851     return currentEnergy(residues);
852   }
853 
854   /**
855    * Uses existing backbone, self, 2-Body, and 3-body energies from rotamerEnergies() to calculate an
856    * approximate energy for a rotamer permutation.
857    *
858    * @param residues Current window of optimization.
859    * @param rotamers Set of rotamers to calculate an approximate energy for.
860    * @param print    Verbosity flag
861    * @return Approximate permutation energy (backbone + selfs + pairs + trimers).
862    */
863   public double computeEnergy(Residue[] residues, int[] rotamers, boolean print) {
864     double selfSum;
865     double pairSum;
866     double threeBodySum;
867     try {
868       if (parallelTeam == null) {
869         parallelTeam = new ParallelTeam();
870       }
871       if (energyRegion == null) {
872         energyRegion = new EnergyRegion(parallelTeam.getThreadCount());
873       }
874       energyRegion.init(eE, residues, rotamers, threeBodyTerm);
875       parallelTeam.execute(energyRegion);
876       selfSum = energyRegion.getSelf();
877       pairSum = energyRegion.getTwoBody();
878       threeBodySum = energyRegion.getThreeBody();
879     } catch (Exception e) {
880       throw new IllegalArgumentException(e);
881     }
882 
883     approximateEnergy = eE.getBackboneEnergy() + selfSum + pairSum + threeBodySum;
884 
885     if (print) {
886       logger.info(format(" Backbone                  %s", formatEnergy(eE.getBackboneEnergy())));
887       logger.info(format(" Self Energy               %s", formatEnergy(selfSum)));
888       logger.info(format(" Pair Energy               %s", formatEnergy(pairSum)));
889       if (!threeBodyTerm) {
890         logger.info(format(" Total Energy up to 2-Body %s", formatEnergy(approximateEnergy)));
891       } else {
892         logger.info(format(" 3-Body Energy             %s", formatEnergy(threeBodySum)));
893         logger.info(format(" Total Energy up to 3-Body %s", formatEnergy(approximateEnergy)));
894       }
895     }
896     return approximateEnergy;
897   }
898 
899   /**
900    * Calculates the energy at the current state.
901    *
902    * @param resArray Array of residues in current energy term.
903    * @return Energy of the current state.
904    */
905   public double currentEnergy(Residue[] resArray) throws ArithmeticException {
906     return currentEnergy(Arrays.asList(resArray));
907   }
908 
909   /**
910    * Wrapper intended for use with RotamerMatrixMC.
911    *
912    * @param resList Reside list.
913    * @return Returns the current energy.
914    * @throws ArithmeticException Thrown if there is an arithmetic exception computing the
915    *                             energy.
916    */
917   public double currentEnergyWrapper(List<Residue> resList) throws ArithmeticException {
918     return currentEnergy(resList);
919   }
920 
921   /**
922    * Forces the use of a ForceFieldEnergyOpenMM's underlying ForceFieldEnergy.
923    *
924    * @return Current potential energy as calculated by FFX reference platform.
925    */
926   public double currentFFXPE() {
927     if (x == null) {
928       int n = potential.getNumberOfVariables();
929       x = new double[n];
930     }
931     potential.getCoordinates(x);
932     return ((OpenMMEnergy) potential).energyFFX(x, false);
933   }
934 
935   /**
936    * Utility method for formatting energies, using 16 spaces with 8 digits of precision.
937    *
938    * @param energy Energy to format.
939    * @return A string representing the energy.
940    */
941   public String formatEnergy(double energy) {
942     if (abs(energy) < 1.0e6) {
943       return format("%16.8f", energy);
944     } else {
945       return format("*%15.4e", energy);
946     }
947   }
948 
949   public double getApproximate() {
950     return approximateEnergy;
951   }
952 
953   public double getBackboneEnergy() {
954     return eE.getBackboneEnergy();
955   }
956 
957   public EliminatedRotamers getEliminatedRotamers() {
958     return eR;
959   }
960 
961   public EnergyExpansion getEnergyExpansion() {
962     return eE;
963   }
964 
965   /**
966    * getEnsemble.
967    *
968    * @return a {@link java.util.List} object.
969    */
970   public List<ResidueState[]> getEnsemble() {
971     if (ensembleStates == null) {
972       return null;
973     } else {
974       List<ResidueState[]> states = new ArrayList<>(ensembleStates.size());
975       ensembleStates.forEach((es) -> states.add(es.val()));
976       return states;
977     }
978   }
979 
980   /**
981    * setEnsemble.
982    *
983    * @param ensemble a int.
984    */
985   public void setEnsemble(int ensemble) {
986     setEnsemble(ensemble, 5.0);
987   }
988 
989   /**
990    * Return the residue list.
991    *
992    * @return a {@link java.util.List} object.
993    */
994   public List<Residue> getResidues() {
995     return residueList;
996   }
997 
998   /**
999    * Set the residue list.
1000    *
1001    * @param residueList a {@link java.util.List} object.
1002    */
1003   public void setResidues(List<Residue> residueList) {
1004     this.residueList = residueList;
1005   }
1006 
1007   /**
1008      * Init fraction array
1009      *
1010      * @param residueList a {@link java.util.List} object.
1011    */
1012     public void initFraction(List<Residue> residueList) {
1013         fraction = new double[residueList.size()][56];
1014         genZ = true;
1015     }
1016 
1017   /**
1018    * Returns the restart file.
1019    *
1020    * @return energyRestartFile File with saved side-chain energies.
1021    */
1022   public File getRestartFile() {
1023     return energyRestartFile;
1024   }
1025 
1026   public double goldsteinPairSumOverK(Residue[] residues, int lb, int ub, int i, int riA, int riB,
1027                                       int j, int rjC, int rjD, List<Residue> blockedResidues, int[] possK) {
1028     double sumOverK = 0.0;
1029 
1030     for (int indK = lb; indK <= ub; indK++) {
1031       int k = possK[indK];
1032       double minForResK = Double.MAX_VALUE;
1033       Residue resk = residues[k];
1034       Rotamer[] rotk = resk.getRotamers();
1035       int nrk = rotk.length;
1036       int rkEvals = 0;
1037       // Loop over residue k's rotamers.
1038       for (int rk = 0; rk < nrk; rk++) {
1039         if (eR.check(k, rk)) {
1040           continue;
1041         }
1042         // Continue if k,rk invalid for riA/rjC.
1043         if (eR.check(i, riA, k, rk) || eR.check(j, rjC, k, rk)) {
1044           // Not implemented: check(i, riA, j, rjC, k, rk).
1045           continue;
1046         }
1047         // Return false if k,rk invalid for riB/rjD.
1048         if (eR.check(i, riB, k, rk) || eR.check(j, rjD, k, rk)) {
1049           blockedResidues.add(resk);
1050           return Double.NaN;
1051         }
1052 
1053         rkEvals++;
1054         double currentResK =
1055             eE.get2Body(i, riA, k, rk) - eE.get2Body(i, riB, k, rk) + eE.get2Body(j, rjC, k, rk)
1056                 - eE.get2Body(j, rjD, k, rk);
1057         // Include 3-body effects.
1058         if (threeBodyTerm) {
1059           double sumOverL = (eE.get3Body(residues, i, riA, j, rjC, k, rk) - eE.get3Body(residues, i,
1060               riB, j, rjD, k, rk));
1061           // Loop over a 4th residue l.
1062           int[] nK = bidiResNeighbors[k];
1063           IntStream lStream = IntStream.concat(Arrays.stream(possK), Arrays.stream(nK));
1064           int[] possL = lStream.filter(l -> (l != i && l != j && l != k)).sorted().distinct()
1065               .toArray();
1066 
1067           for (int l : possL) {
1068             if (l == k || l == i || l == j) {
1069               continue;
1070             }
1071             Residue residuel = residues[l];
1072             Rotamer[] rotamersl = residuel.getRotamers();
1073             int nrl = rotamersl.length;
1074             int rlEvaluations = 0;
1075             double minForResL = Double.MAX_VALUE;
1076             // Loop over rotamers for residue l.
1077             for (int rl = 0; rl < nrl; rl++) {
1078               // If not a part of valid phase space for riA/rjC, continue.
1079               if (eR.check(l, rl) || eR.check(k, rk, l, rl) || eR.check(i, riA, l, rl) || eR.check(j,
1080                   rjC, l, rl)) {
1081                 // Not implemented: check(i, riA, j, rjC, l, rl) || check(i, riA, k, rk, l, rl) ||
1082                 // check(j, rjC, k, rk, l, rl) || check(i, riA, j, rjC, k, rk, l, rl)
1083                 continue;
1084               }
1085               if (eR.check(i, riB, l, rl) || eR.check(j, rjD, l, rl)) {
1086                 // Not implemented: check(i, riB, j, rjD, l, rl) || check(i, riB, k, rk, l, rl) ||
1087                 // check(j, rjD, k, rk, l, rl) || check(i, riB, j, rjD, k, rk, l, rl)
1088                 blockedResidues.add(residuel);
1089                 return Double.NaN;
1090               }
1091               rlEvaluations++;
1092               double e =
1093                   eE.get3Body(residues, i, riA, k, rk, l, rl) - eE.get3Body(residues, i, riB, k, rk,
1094                       l, rl) + eE.get3Body(residues, j, rjC, k, rk, l, rl) - eE.get3Body(residues, j,
1095                       rjD, k, rk, l, rl);
1096               if (e < minForResL) {
1097                 minForResL = e;
1098               }
1099             }
1100             if (rlEvaluations == 0) {
1101               minForResL = 0.0;
1102             }
1103             sumOverL += minForResL;
1104           }
1105           currentResK += sumOverL;
1106         }
1107         if (currentResK < minForResK) {
1108           minForResK = currentResK;
1109         }
1110       }
1111       if (rkEvals == 0) {
1112         minForResK = 0.0;
1113         blockedResidues.add(resk);
1114       }
1115       sumOverK += minForResK;
1116     }
1117     return sumOverK;
1118   }
1119 
1120   public void logIfRank0(String msg) {
1121     if (rank0) {
1122       logger.info(msg);
1123     }
1124   }
1125 
1126   public void logIfRank0(String msg, Level level) {
1127     if (rank0) {
1128       logger.log(level, msg);
1129     }
1130   }
1131 
1132   /**
1133    * Perform the rotamer optimization using the specified algorithm.
1134    *
1135    * @param algorithm a {@link RotamerOptimization.Algorithm} object.
1136    * @return the lowest energy found.
1137    */
1138   public double optimize(Algorithm algorithm) {
1139     this.algorithm = algorithm;
1140     return optimize();
1141   }
1142 
1143   /**
1144    * Execute the rotamer optimization.
1145    *
1146    * @return The lowest energy found.
1147    */
1148   public double optimize() {
1149     double e = 0;
1150     try {
1151 
1152       CompositeConfiguration properties = molecularAssembly.getProperties();
1153       boolean ignoreNA = properties.getBoolean("ignoreNA", false);
1154       if (ignoreNA) {
1155         logger.info(" Ignoring nucleic acids.");
1156       }
1157 
1158       logger.info(format("\n Rotamer Library:     %s", library.getLibrary()));
1159       logger.info(format(" Algorithm:           %s", algorithm));
1160       logger.info(format(" Goldstein Criteria:  %b", useGoldstein));
1161       logger.info(format(" Three-Body Energies: %b\n", threeBodyTerm));
1162 
1163       /*
1164        * Collect all residues in the MolecularAssembly. Use all Residues with Rotamers.
1165        */
1166       allResiduesList = new ArrayList<>();
1167       // An array of polymers from the MolecularAssembly.
1168       Polymer[] polymers = molecularAssembly.getChains();
1169       for (Polymer polymer : polymers) {
1170         List<Residue> current = polymer.getResidues();
1171         for (Residue residuej : current) {
1172           residuej.setRotamers(library);
1173           if (residuej.getRotamers() != null) {
1174             if (!(ignoreNA && residuej.getResidueType() == Residue.ResidueType.NA)) {
1175               allResiduesList.add(residuej);
1176             }
1177           }
1178         }
1179       }
1180 
1181       sortResidues(allResiduesList);
1182       sortResidues(residueList);
1183 
1184       // If -DignoreNA=true, then remove nucleic acids from residue list.
1185       if (ignoreNA) {
1186         List<Residue> onlyAA = new ArrayList<>();
1187         for (Residue res : residueList) {
1188           if (res.getResidueType() != Residue.ResidueType.NA) {
1189             onlyAA.add(res);
1190           }
1191         }
1192         residueList = onlyAA;
1193       }
1194 
1195       RotamerLibrary.initializeDefaultAtomicCoordinates(
1196           molecularAssembly.getChains()); // for NA only
1197 
1198       nAllResidues = allResiduesList.size();
1199       allResiduesArray = allResiduesList.toArray(new Residue[nAllResidues]);
1200 
1201       /*
1202        * Distance matrix is used to add residues to the sliding window based on distance cutoff,
1203        * and for cutoff distances.
1204        *
1205        * The memory and compute overhead can be a problem for some very large structures.
1206        */
1207       if (distance > 0) {
1208         dM = new DistanceMatrix(molecularAssembly, algorithmListener, allResiduesArray,
1209             allResiduesList, distanceMethod, distance, twoBodyCutoffDist, threeBodyCutoffDist);
1210       }
1211 
1212       if (residueList != null) {
1213 
1214         // Allocate memory for storing optimal rotamers.
1215         optimum = new int[residueList.size()];
1216 
1217         done = false;
1218         terminate = false;
1219 
1220         switch (algorithm) {
1221           case INDEPENDENT -> e = independent(residueList);
1222           case BRUTE_FORCE -> e = bruteForce(residueList);
1223           case ALL -> {
1224             e = globalOptimization(residueList);
1225             arraycopy(optimumSubset, 0, optimum, 0, residueList.size());
1226           }
1227             case WINDOW -> {
1228                 if(genZ) {
1229                     e = slidingWindowCentered(residueList);
1230                 } else {
1231                     e = slidingWindowOptimization(residueList, windowSize, increment, revert, distance,
1232                             direction, -1);
1233                 }
1234             }
1235             case BOX -> e = boxOpt.boxOptimization(residueList);
1236           default -> {
1237           }
1238         }
1239         terminate = false;
1240         done = true;
1241       }
1242     } catch (Exception exception) {
1243         exception.printStackTrace();
1244     } finally {
1245       try {
1246         if (energyWriter != null) {
1247           energyWriter.close();
1248         }
1249       } catch (IOException ex) {
1250         logger.severe(" Exception in closing buffered energy writer.");
1251       }
1252     }
1253 
1254     return e;
1255   }
1256 
1257   /**
1258    * A brute-force global optimization over side-chain rotamers using a recursive algorithm.
1259    *
1260    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
1261    * @param residues          an array of {@link ffx.potential.bonded.Residue} objects.
1262    * @param i                 a int.
1263    * @param lowEnergy         a double.
1264    * @param optimum           the minimum energy rotamer indices for each residue.
1265    * @return the current energy.
1266    */
1267   public double rotamerOptimization(MolecularAssembly molecularAssembly, Residue[] residues, int i,
1268                                     double lowEnergy, int[] optimum) {
1269 
1270     // This is the initialization condition.
1271     if (i == 0) {
1272       evaluatedPermutations = 0;
1273     }
1274 
1275     int nResidues = residues.length;
1276     Residue current = residues[i];
1277     Rotamer[] rotamers = current.getRotamers();
1278     int lenri = rotamers.length;
1279     double currentEnergy = Double.MAX_VALUE;
1280     List<Residue> resList = Arrays.asList(residues);
1281     if (i < nResidues - 1) {
1282       // As long as there are more residues, continue the recursion for each rotamer of the current
1283       // residue.
1284       int minRot = -1;
1285       for (int ri = 0; ri < lenri; ri++) {
1286         applyRotamer(current, rotamers[ri]);
1287         double rotEnergy = rotamerOptimization(molecularAssembly, residues, i + 1, lowEnergy,
1288             optimum);
1289         if (rotEnergy < currentEnergy) {
1290           currentEnergy = rotEnergy;
1291         }
1292         if (rotEnergy < lowEnergy) {
1293           minRot = ri;
1294           lowEnergy = rotEnergy;
1295         }
1296       }
1297       if (minRot > -1) {
1298         optimum[i] = minRot;
1299       }
1300     } else {
1301       /*
1302        At the end of the recursion, compute the potential energy for each rotamer of the final
1303        residue. If a lower potential energy is discovered, the rotamers of each residue will be
1304        collected as the recursion returns up the chain.
1305       */
1306       int[] rotArray = copyOf(optimum, nResidues);
1307       for (int ri = 0; ri < lenri; ri++) {
1308         applyRotamer(current, rotamers[ri]);
1309         double rotEnergy = Double.NaN;
1310         try {
1311           rotArray[nResidues - 1] = ri;
1312           rotEnergy = currentEnergy(resList) + eE.getTotalRotamerPhBias(resList, rotArray, pH, pHRestraint);
1313           logger.info(format(" %d Energy: %s", ++evaluatedPermutations, formatEnergy(rotEnergy)));
1314         } catch (ArithmeticException ex) {
1315           logger.info(
1316               format(" %d Energy set to NaN (unreasonable conformation)", ++evaluatedPermutations));
1317         }
1318         if (algorithmListener != null) {
1319           algorithmListener.algorithmUpdate(molecularAssembly);
1320         }
1321         if (rotEnergy < currentEnergy) {
1322           currentEnergy = rotEnergy;
1323         }
1324         if (rotEnergy < lowEnergy) {
1325           lowEnergy = rotEnergy;
1326           optimum[nResidues - 1] = ri;
1327         }
1328       }
1329     }
1330     return currentEnergy;
1331   }
1332 
1333     /**
1334      * Set the K for the harmonic pH restraint
1335      * @param pHRestraint KpH
1336      */
1337     public void setPHRestraint(double pHRestraint) {this.pHRestraint = pHRestraint;}
1338     /**
1339      * Set the environment pH
1340      * @param pH
1341      */
1342     public void setpH(double pH) {
1343         this.pH = pH;
1344     }
1345     /**
1346      * Sets to recompute self energies at a different pH using an energy restart file
1347      * @param recomputeSelf
1348      */
1349     public void setRecomputeSelf(boolean recomputeSelf) {
1350         this.recomputeSelf = recomputeSelf;
1351     }
1352 
1353     /**
1354      * Return the K in the harmonic pH restraint
1355      * @return double KpH
1356      */
1357     public double getPHRestraint() {
1358         return pHRestraint;
1359     }
1360 
1361     /**
1362      * Get the enviroment pH
1363      * @return double pH
1364      */
1365     public double getPH() {
1366         return pH;
1367     }
1368   /**
1369    * Sets the approximate dimensions of boxes, over-riding numXYZBoxes in determining box size.
1370    * Rounds box size up and number of boxes down to get a whole number of boxes along each axis.
1371    *
1372    * @param approxBoxLength Optional box dimensions parameter (Angstroms).
1373    */
1374   public void setApproxBoxLength(double approxBoxLength) {
1375     boxOpt.approxBoxLength = approxBoxLength;
1376   }
1377 
1378   /**
1379    * Sets the amount of overlap between adjacent boxes for box optimization.
1380    *
1381    * @param boxBorderSize Box overlap in Angstroms.
1382    */
1383   public void setBoxBorderSize(double boxBorderSize) {
1384     boxOpt.cellBorderSize = boxBorderSize;
1385   }
1386 
1387   /**
1388    * Set the ending box index.
1389    *
1390    * @param boxEnd a int.
1391    */
1392   public void setBoxEnd(int boxEnd) {
1393     // Is -1 if boxes run to completion.
1394     boxOpt.cellEnd = boxEnd;
1395   }
1396 
1397   /**
1398    * Sets behavior for how Residues are added to boxOptCells; 1 uses just reference atom (C alpha for
1399    * protein, N1/9 for nucleic acids), 2 uses any atom, 3 uses any atom in any rotamer.
1400    *
1401    * @param boxInclusionCriterion Criterion to use
1402    */
1403   public void setBoxInclusionCriterion(int boxInclusionCriterion) {
1404     boxOpt.boxInclusionCriterion = boxInclusionCriterion;
1405   }
1406 
1407   /**
1408    * Set the starting box index.
1409    *
1410    * @param boxStart a int.
1411    */
1412   public void setBoxStart(int boxStart) {
1413     boxOpt.cellStart = boxStart;
1414   }
1415 
1416   /**
1417    * Sets titratble residues to be the center of the box.
1418    *
1419    * @param titrationBoxes a boolean.
1420    */
1421   public void setTitrationBoxes(boolean titrationBoxes) {boxOpt.titrationBoxes = titrationBoxes;}
1422 
1423     /**
1424      * Sets the size around the titratable residues.
1425      *
1426      * @param titrationBoxSize double size of the titration box.
1427      */
1428     public void setTitrationBoxSize(double titrationBoxSize) {boxOpt.titrationBoxSize = titrationBoxSize;}
1429 
1430   /**
1431    * setCoordinatesToEnsemble.
1432    *
1433    * @param ensnum a int.
1434    */
1435   public void setCoordinatesToEnsemble(int ensnum) {
1436     if (ensembleStates != null && !ensembleStates.isEmpty()) {
1437       ensnum %= ensembleStates.size();
1438       ResidueState.revertAllCoordinates(residueList, ensembleStates.get(ensnum).val());
1439     } else {
1440       throw new IllegalArgumentException(" Ensemble states not initialized!");
1441     }
1442   }
1443 
1444   /**
1445    * Sets the decompose-original flag.
1446    *
1447    * @param decomposeOriginal If true, decompose the energy of the structure without optimizing.
1448    */
1449   public void setDecomposeOriginal(boolean decomposeOriginal) {
1450     this.decomposeOriginal = decomposeOriginal;
1451   }
1452 
1453   /**
1454    * Set the optimization direction to forward or backward.
1455    *
1456    * @param direction a {@link RotamerOptimization.Direction} object.
1457    */
1458   public void setDirection(Direction direction) {
1459     this.direction = direction;
1460   }
1461 
1462   /**
1463    * Set the cut-off distance for inclusion of residues in sliding box and window methods.
1464    *
1465    * @param distance a double.
1466    */
1467   public void setDistanceCutoff(double distance) {
1468     this.distance = distance;
1469   }
1470 
1471   /**
1472    * Setter for the field <code>energyRestartFile</code>.
1473    *
1474    * @param file a {@link java.io.File} object.
1475    */
1476   public void setEnergyRestartFile(File file) {
1477     loadEnergyRestart = true;
1478     energyRestartFile = file;
1479   }
1480 
1481   /**
1482    * setEnsemble.
1483    *
1484    * @param ensemble       a int.
1485    * @param ensembleBuffer a double.
1486    */
1487   public void setEnsemble(int ensemble, double ensembleBuffer) {
1488     this.ensembleNumber = ensemble;
1489     this.ensembleBuffer = ensembleBuffer;
1490     this.ensembleBufferStep = 0.1 * ensembleBuffer;
1491     if (ensemble > 1) {
1492       setPruning(0);
1493     }
1494   }
1495 
1496   /**
1497    * Set the residue increment for sliding window.
1498    *
1499    * @param increment a int.
1500    */
1501   public void setIncrement(int increment) {
1502     this.increment = increment;
1503   }
1504 
1505   /**
1506    * Control the depth of self-consistency checking with a rotamer is eliminated.
1507    *
1508    * @param maxRotCheckDepth a int.
1509    */
1510   public void setMaxRotCheckDepth(int maxRotCheckDepth) {
1511     this.maxRotCheckDepth = maxRotCheckDepth;
1512   }
1513 
1514   /**
1515    * Set the minimum number of accepted nucleic acid rotamers.
1516    *
1517    * @param minNumberAcceptedNARotamers a int.
1518    */
1519   public void setMinimumNumberAcceptedNARotamers(int minNumberAcceptedNARotamers) {
1520     if (minNumberAcceptedNARotamers > 0) {
1521       this.minNumberAcceptedNARotamers = minNumberAcceptedNARotamers;
1522     } else {
1523       logger.warning(
1524           "\n Minimum number of accepted NA rotamers must be a positive integer.\n Setting to default value 10.\n");
1525       this.minNumberAcceptedNARotamers = 10;
1526     }
1527   }
1528 
1529   /**
1530    * Sets the option to use a number of Monte Carlo steps for final optimization.
1531    *
1532    * @param monteCarlo If Monte Carlo is to be considered
1533    * @param nMCsteps   Number of steps to be taken
1534    */
1535   public void setMonteCarlo(boolean monteCarlo, int nMCsteps) {
1536     this.monteCarlo = monteCarlo;
1537     this.nMCSteps = nMCsteps;
1538   }
1539 
1540   /**
1541    * Sets the monteCarloTesting boolean in RotamerOptimization.java to true or false. This should
1542    * only be set to true when monte carlo is being tested through the ManyBodyTest script. When true,
1543    * the method sets a seed for the pseudo-random number generator and allows the monte carlo rotamer
1544    * optimization to be deterministic.
1545    *
1546    * @param bool True or false.
1547    */
1548   public void setMonteCarloTesting(boolean bool) {
1549     this.monteCarloTesting = bool;
1550   }
1551 
1552   /**
1553    * The nucleic acid correction threshold.
1554    *
1555    * @param nucleicCorrectionThreshold a double.
1556    */
1557   public void setNucleicCorrectionThreshold(double nucleicCorrectionThreshold) {
1558     if (nucleicCorrectionThreshold >= 0) {
1559       this.nucleicCorrectionThreshold = nucleicCorrectionThreshold;
1560     } else {
1561       logger.warning(
1562           "\n Correction threshold must be >= 0. Setting to default of 0 (threshold inactive).\n");
1563       this.nucleicCorrectionThreshold = 0;
1564     }
1565   }
1566 
1567   /**
1568    * Also sets derivative pruning factors.
1569    *
1570    * @param nucleicPruningFactor a double.
1571    */
1572   public void setNucleicPruningFactor(double nucleicPruningFactor) {
1573     this.nucleicPruningFactor = nucleicPruningFactor;
1574     this.nucleicPairsPruningFactor = ((1.0 + nucleicPruningFactor) / 2);
1575   }
1576 
1577   /**
1578    * Sets the number of boxes in the x, y, and z axes if the box optimization is to be carried out.
1579    *
1580    * @param numXYZBoxes Int[3] of number of boxes in x, y, z.
1581    */
1582   public void setNumXYZBoxes(int[] numXYZBoxes) {
1583     arraycopy(numXYZBoxes, 0, boxOpt.numXYZCells, 0, boxOpt.numXYZCells.length);
1584   }
1585 
1586   /**
1587    * Setter for the field <code>pairClashThreshold</code>.
1588    *
1589    * @param pairClashThreshold a double.
1590    */
1591   public void setPairClashThreshold(double pairClashThreshold) {
1592     this.pairClashThreshold = pairClashThreshold;
1593   }
1594 
1595   /**
1596    * Sets whether rotamer optimization should print out any files, or act solely to optimize a
1597    * structure in memory.
1598    *
1599    * @param printFiles a boolean.
1600    */
1601   public void setPrintFiles(boolean printFiles) {
1602     this.printFiles = printFiles;
1603   }
1604 
1605   /**
1606    * Sets level of pruning: 0 for fully off, 1 for only singles, 2 for single and pair pruning.
1607    *
1608    * @param set Pruning option.
1609    */
1610   public void setPruning(int set) {
1611     if (set == 0) {
1612       this.pruneClashes = false;
1613       this.prunePairClashes = false;
1614     } else if (set == 1) {
1615       this.pruneClashes = true;
1616       this.prunePairClashes = false;
1617     } else if (set == 2) {
1618       this.pruneClashes = true;
1619       this.prunePairClashes = true;
1620     }
1621   }
1622 
1623   /**
1624    * Accepts a list of residues but throws out null residues. Used by the -lR flag.
1625    *
1626    * @param residues a {@link java.util.List} object.
1627    */
1628   public void setResiduesIgnoreNull(List<Residue> residues) {
1629     residueList = new ArrayList<>();
1630     logger.fine(" Optimizing these residues: ");
1631     for (Residue r : residues) {
1632       if (r.getRotamers() != null) {
1633         residueList.add(r);
1634         logger.fine(format("\t%s", r));
1635       } else {
1636         logger.fine(format(" not \t%s", r));
1637       }
1638     }
1639   }
1640 
1641   /**
1642    * Set the algorithm to revert to starting coordinates if the energy increases.
1643    *
1644    * @param revert a boolean.
1645    */
1646   public void setRevert(boolean revert) {
1647     this.revert = revert;
1648   }
1649 
1650   public void setRotamerLibrary(RotamerLibrary lib) {
1651     library = lib;
1652   }
1653 
1654   /**
1655    * setSingletonClashThreshold.
1656    *
1657    * @param singletonClashThreshold a double.
1658    */
1659   public void setSingletonClashThreshold(double singletonClashThreshold) {
1660     this.clashThreshold = singletonClashThreshold;
1661   }
1662 
1663   /**
1664    * Setter for the field <code>superpositionThreshold</code>.
1665    *
1666    * @param superpositionThreshold a double.
1667    */
1668   public void setSuperpositionThreshold(double superpositionThreshold) {
1669     this.superpositionThreshold = superpositionThreshold;
1670   }
1671 
1672   /**
1673    * Sets the threeBodyCutoffDist. All three-body energies where the rotamers have a separation
1674    * distance larger than the cutoff are set to 0.
1675    *
1676    * @param threeBodyCutoffDist Separation distance at which the interaction of three side-chains
1677    *                            is assumed to have an energy of 0.
1678    */
1679   public void setThreeBodyCutoff(double threeBodyCutoffDist) {
1680     this.threeBodyCutoffDist = threeBodyCutoffDist;
1681     if (this.threeBodyCutoffDist < 0) {
1682       logger.info("Warning: threeBodyCutoffDist should not be less than 0.");
1683     }
1684   }
1685 
1686 
1687   /**
1688    * Flag to control use of 3-body energy terms.
1689    *
1690    * @param threeBodyTerm a boolean.
1691    */
1692   public void setThreeBodyEnergy(boolean threeBodyTerm) {
1693     this.threeBodyTerm = threeBodyTerm;
1694   }
1695 
1696   /**
1697    * Sets the twoBodyCutoffDist. All two-body energies where the rotamers have a separation distance
1698    * larger than the cutoff are set to 0.
1699    *
1700    * @param twoBodyCutoffDist Separation distance at which the interaction of two side-chains is
1701    *                          assumed to have an energy of 0.
1702    */
1703   public void setTwoBodyCutoff(double twoBodyCutoffDist) {
1704     this.twoBodyCutoffDist = twoBodyCutoffDist;
1705     if (this.twoBodyCutoffDist < 0) {
1706       logger.info("Warning: threeBodyCutoffDist should not be less than 0.");
1707     }
1708   }
1709 
1710   /**
1711    * Specify use of Goldstein optimization.
1712    *
1713    * @param useGoldstein a boolean.
1714    */
1715   public void setUseGoldstein(boolean useGoldstein) {
1716     this.useGoldstein = useGoldstein;
1717   }
1718 
1719   /**
1720    * Setter for the field <code>windowSize</code>.
1721    *
1722    * @param windowSize a int.
1723    */
1724   public void setWindowSize(int windowSize) {
1725     this.windowSize = windowSize;
1726     if (this.increment > windowSize) {
1727       logger.info(format(" Decreasing increment to match window size %d", windowSize));
1728       this.increment = windowSize;
1729     }
1730   }
1731 
1732   /**
1733    * {@inheritDoc}
1734    */
1735   @Override
1736   public void terminate() {
1737     terminate = true;
1738     while (!done) {
1739       synchronized (this) {
1740         try {
1741           wait(1);
1742         } catch (InterruptedException e) {
1743           logger.log(Level.WARNING, " Exception terminating rotamer optimization.\n", e);
1744         }
1745       }
1746     }
1747   }
1748 
1749   /**
1750    * {@inheritDoc}
1751    */
1752   @Override
1753   public String toString() {
1754     if (eR != null) {
1755       return eR.toString();
1756     } else {
1757       return null;
1758     }
1759   }
1760 
1761   public void turnOffAllResidues(Residue[] residues) {
1762     if (residues == null) {
1763       return;
1764     }
1765     for (Residue residue : residues) {
1766       turnOffResidue(residue);
1767     }
1768   }
1769 
1770   public void turnOffResidue(Residue residue) {
1771     turnOffAtoms(residue);
1772     applyDefaultRotamer(residue);
1773   }
1774 
1775   public void turnOnAllResidues(Residue[] residues) {
1776     if (residues == null) {
1777       return;
1778     }
1779     for (Residue residue : residues) {
1780       turnOnAtoms(residue);
1781     }
1782   }
1783 
1784   public void turnOnResidue(Residue residue, int ri) {
1785     turnOnAtoms(residue);
1786     Rotamer[] rotamers = residue.getRotamers();
1787     applyRotamer(residue, rotamers[ri]);
1788   }
1789 
1790   /**
1791    * ONLY FOR UNIT TESTING. Sets a boolean to turn the pair elimination criteria off.
1792    */
1793   public void turnRotamerPairEliminationOff() {
1794     logger.info(" Turning off pair eliminations.");
1795     pairEliminationOn = false;
1796   }
1797 
1798   /**
1799    * ONLY FOR UNIT TESTING. Sets a boolean to turn the self elimination criteria off.
1800    */
1801   public void turnRotamerSingleEliminationOff() {
1802     logger.info(" Turning off single eliminations.");
1803     selfEliminationOn = false;
1804   }
1805 
1806   /**
1807    * Recursive brute-force method which uses single, pair, and potentially trimer energies to
1808    * calculate an optimum set of rotamers.
1809    *
1810    * @param residues        Optimization window
1811    * @param i               Current residue in the recursion.
1812    * @param lowEnergy       Minimum energy yet found by the recursion.
1813    * @param optimum         Optimum rotamer set yet found by the recursion.
1814    * @param currentRotamers Rotamer permutation under investigation.
1815    * @return Minimum energy found under this node in the recursion.
1816    */
1817   private double decomposedRotamerOptimization(Residue[] residues, int i, double lowEnergy,
1818                                                int[] optimum, int[] currentRotamers) {
1819 
1820     // This is the initialization condition.
1821     if (i == 0) {
1822       evaluatedPermutations = 0;
1823     }
1824 
1825     int nResidues = residues.length;
1826     Residue current = residues[i];
1827     Rotamer[] rotamers = current.getRotamers();
1828     int lenri = rotamers.length;
1829     double currentEnergy = Double.MAX_VALUE;
1830     if (i < nResidues - 1) {
1831       // As long as there are more residues, continue the recursion for each rotamer of the current
1832       // residue.
1833       for (int ri = 0; ri < lenri; ri++) {
1834         if (eR.check(i, ri)) {
1835           continue;
1836         }
1837         currentRotamers[i] = ri;
1838         double rotEnergy = decomposedRotamerOptimization(residues, i + 1, lowEnergy, optimum,
1839             currentRotamers);
1840         if (rotEnergy < lowEnergy) {
1841           lowEnergy = rotEnergy;
1842         }
1843         if (rotEnergy < currentEnergy) {
1844           currentEnergy = rotEnergy;
1845         }
1846       }
1847     } else {
1848       // At the end of the recursion, compute the potential energy for each rotamer of the final
1849       // residue and update optimum[].
1850       for (int ri = 0; ri < lenri; ri++) {
1851         if (eR.check(i, ri)) {
1852           continue;
1853         }
1854         currentRotamers[i] = ri;
1855         double rotEnergy = computeEnergy(residues, currentRotamers, false);
1856         ++evaluatedPermutations;
1857         if (rotEnergy < currentEnergy) {
1858           currentEnergy = rotEnergy;
1859         }
1860         if (rotEnergy < lowEnergy) {
1861           /*
1862            Because we print the rotamer set immediately on finding a
1863            more optimal structure, we have to reset the entire length
1864            of optimum instead of lazily doing it on the way down.
1865           */
1866           arraycopy(currentRotamers, 0, optimum, 0, optimum.length);
1867           if (evaluatedPermutations > 1) {
1868             logger.info(
1869                 format(" Minimum energy update: %f < %f, permutation %d", rotEnergy, lowEnergy,
1870                     evaluatedPermutations));
1871             String permutation = " Rotamer permutation: " + optimum[0];
1872             for (int j = 1; j < nResidues; j++) {
1873               permutation = permutation.concat(", " + optimum[j]);
1874             }
1875             logger.info(permutation);
1876           } else {
1877             logger.info(format(" First minimum energy (permutation 1): %f", rotEnergy));
1878           }
1879           lowEnergy = rotEnergy;
1880         }
1881       }
1882     }
1883     return currentEnergy;
1884   }
1885 
1886   /**
1887    * Runs Monte Carlo side chain optimization using the rotamer energy matrix and potentially some
1888    * information from dead-end or Goldstein elimination. The useAllElims variable should be set false
1889    * if detailed balance is to be maintained. At present, no support for ensembles.
1890    *
1891    * @param residues      Optimization window
1892    * @param optimum       Array to store optimum rotamers
1893    * @param initialRots   Array with starting rotamers
1894    * @param maxIters      Number of MC steps to run
1895    * @param randomizeRots Scramble initialRots
1896    * @param useAllElims   Use pair/triple elimination information
1897    * @return Lowest energy found
1898    */
1899   private double rotamerOptimizationMC(Residue[] residues, int[] optimum, int[] initialRots,
1900                                        int maxIters, boolean randomizeRots, boolean useAllElims) {
1901 
1902     long initTime = -System.nanoTime();
1903     if (randomizeRots) {
1904       randomizeRotamers(initialRots, residues, true);
1905     }
1906 
1907     int nRes = residues.length;
1908     arraycopy(initialRots, 0, optimum, 0, nRes);
1909     assert optimum.length == nRes;
1910     assert initialRots.length == nRes;
1911 
1912     RotamerMatrixMC rmc = new RotamerMatrixMC(initialRots, residues, useForceFieldEnergy, this);
1913     rmc.setTemperature(mcTemp);
1914     RotamerMatrixMove rmove = new RotamerMatrixMove(useAllElims, initialRots, residues, this, eR,
1915         monteCarloTesting);
1916     List<MCMove> rmList = new ArrayList<>(1);
1917     rmList.add(rmove);
1918 
1919     double initialEnergy = computeEnergy(residues, initialRots, false);
1920     double optimumEnergy = initialEnergy;
1921     double currentEnergy = initialEnergy;
1922 
1923     int nAccept = 0;
1924     logIfRank0(
1925         format(" Beginning %d iterations of Monte Carlo search " + "starting from energy %10.6f",
1926             maxIters, initialEnergy));
1927 
1928     for (int i = 0; i < maxIters; i++) {
1929       if (rmc.mcStep(rmList, currentEnergy)) {
1930         currentEnergy = rmc.lastEnergy();
1931         ++nAccept;
1932         if (currentEnergy < optimumEnergy) {
1933           optimumEnergy = currentEnergy;
1934           arraycopy(initialRots, 0, optimum, 0, nRes);
1935         }
1936       } else {
1937         currentEnergy = rmc.lastEnergy();
1938       }
1939     }
1940 
1941     initTime += System.nanoTime();
1942     double fractAccept = ((double) nAccept) / ((double) maxIters);
1943     logIfRank0(
1944         format(" %d steps of DEE-MC completed in %10.6f seconds", maxIters, (initTime * 1.0E-9)));
1945     logIfRank0(format(" Number of steps accepted: %d for %10.6f of total", nAccept, fractAccept));
1946     logIfRank0(format(" Lowest energy found: %10.6f kcal/mol", optimumEnergy));
1947     logIfRank0(format(" Final energy found: %10.6f kcal/mol", currentEnergy));
1948 
1949     return optimumEnergy;
1950   }
1951 
1952   /**
1953    * Scrambles an array of rotamers.
1954    *
1955    * @param rotamers    Array of rotamers.
1956    * @param residues    Array of residues.
1957    * @param useAllElims If trye, use all elliminations.
1958    */
1959   private void randomizeRotamers(int[] rotamers, Residue[] residues, boolean useAllElims) {
1960     int nRes = rotamers.length;
1961     for (int i = 0; i < nRes; i++) {
1962       Rotamer[] rotsi = residues[i].getRotamers();
1963       int lenri = rotsi.length;
1964       List<Integer> allowedRots = new ArrayList<>(lenri);
1965 
1966       for (int ri = 0; ri < lenri; ri++) {
1967         if (!eR.check(i, ri)) {
1968           allowedRots.add(ri);
1969         }
1970       }
1971 
1972       Random rand = new Random();
1973       int nRots = allowedRots.size();
1974       if (monteCarloTesting) {
1975         rand.setSeed(nRots);
1976       }
1977       if (nRots > 1) {
1978         boolean validMove = !useAllElims;
1979         int indexRI;
1980         do {
1981           int ri = rand.nextInt(nRots);
1982           indexRI = allowedRots.get(ri);
1983           if (useAllElims) {
1984             validMove = checkValidMove(i, indexRI, rotamers);
1985           }
1986         } while (!validMove);
1987         rotamers[i] = indexRI;
1988       }
1989     }
1990   }
1991 
1992   /**
1993    * A global optimization over side-chain rotamers using a recursive algorithm and information about
1994    * eliminated rotamers, rotamer pairs and rotamer triples
1995    *
1996    * @param molecularAssembly   MolecularAssmebly to use.
1997    * @param residues            Array of residues.
1998    * @param i                   Current permutation.
1999    * @param currentRotamers     Current array of rotamers.
2000    * @param lowEnergy           Lowest energy found.
2001    * @param optimum             Optimum set of rotamers.
2002    * @param permutationEnergies Energies of visited permutations or null.
2003    * @return current energy.
2004    */
2005   private double rotamerOptimizationDEE(MolecularAssembly molecularAssembly, Residue[] residues,
2006                                         int i, int[] currentRotamers, double lowEnergy, int[] optimum, double[] permutationEnergies) {
2007     // This is the initialization condition.
2008     if (i == 0) {
2009       evaluatedPermutations = 0;
2010     }
2011 
2012     int nResidues = residues.length;
2013     Residue residuei = residues[i];
2014     Rotamer[] rotamersi = residuei.getRotamers();
2015     int lenri = rotamersi.length;
2016     double currentEnergy = Double.MAX_VALUE;
2017     List<Residue> resList = Arrays.asList(residues);
2018 
2019     // As long as there are more residues, continue the recursion for each rotamer of the current
2020     // residue.
2021     if (i < nResidues - 1) {
2022       // Loop over rotamers of residue i.
2023       for (int ri = 0; ri < lenri; ri++) {
2024         // Check if rotamer ri has been eliminated by DEE.
2025         if (eR.check(i, ri)) {
2026           continue;
2027         }
2028         // Check if rotamer ri has been eliminated by an upstream rotamer (any residue's rotamer
2029         // from j = 0 .. i-1).
2030         boolean deadEnd = false;
2031         for (int j = 0; j < i; j++) {
2032           int rj = currentRotamers[j];
2033           deadEnd = eR.check(j, rj, i, ri);
2034           if (deadEnd) {
2035             break;
2036           }
2037         }
2038         if (deadEnd) {
2039           continue;
2040         }
2041         applyRotamer(residuei, rotamersi[ri]);
2042         currentRotamers[i] = ri;
2043         double rotEnergy = rotamerOptimizationDEE(molecularAssembly, residues, i + 1,
2044             currentRotamers, lowEnergy, optimum, permutationEnergies);
2045         if (rotEnergy < currentEnergy) {
2046           currentEnergy = rotEnergy;
2047         }
2048         if (rotEnergy < lowEnergy) {
2049           optimum[i] = ri;
2050           lowEnergy = rotEnergy;
2051         }
2052       }
2053     } else {
2054       if (ensembleStates == null) {
2055         ensembleStates = new ArrayList<>();
2056       }
2057 
2058       /*
2059        At the end of the recursion, compute the potential energy for each rotamer of the final
2060        residue. If a lower potential energy is discovered, the rotamers of each residue will be
2061        collected as the recursion returns up the chain.
2062       */
2063       for (int ri = 0; ri < lenri; ri++) {
2064         // Check if rotamer ri has been eliminated by DEE.
2065         if (eR.check(i, ri)) {
2066           continue;
2067         }
2068         currentRotamers[i] = ri;
2069         // Check if rotamer ri has been eliminated by an upstream rotamer (any residue's rotamer
2070         // from 0 .. i-1).
2071         boolean deadEnd = false;
2072         for (int j = 0; j < i; j++) {
2073           int rj = currentRotamers[j];
2074           deadEnd = eR.check(j, rj, i, ri);
2075           if (deadEnd) {
2076             break;
2077           }
2078         }
2079         if (deadEnd) {
2080           continue;
2081         }
2082         applyRotamer(residuei, rotamersi[ri]);
2083         // Compute the energy based on a 3-body approximation
2084         approximateEnergy = computeEnergy(residues, currentRotamers, false);
2085         double comparisonEnergy = approximateEnergy;
2086         evaluatedPermutations++;
2087         // Compute the AMOEBA energy
2088         if (useForceFieldEnergy) {
2089           double amoebaEnergy = Double.NaN;
2090           try {
2091             // Add the rotamer pH bias to the force field energy.
2092             amoebaEnergy =
2093                 currentEnergy(resList) + eE.getTotalRotamerPhBias(resList, currentRotamers, pH, pHRestraint);
2094           } catch (ArithmeticException ex) {
2095             logger.warning(
2096                 format(" Exception %s in calculating full AMOEBA energy for permutation %d", ex,
2097                     evaluatedPermutations));
2098           }
2099           comparisonEnergy = amoebaEnergy;
2100         }
2101         if (permutationEnergies != null) {
2102           permutationEnergies[evaluatedPermutations - 1] = comparisonEnergy;
2103         }
2104         if (algorithmListener != null) {
2105           algorithmListener.algorithmUpdate(molecularAssembly);
2106         }
2107         if (ensembleNumber > 1) {
2108           if (rank0 && printFiles) {
2109             try {
2110               FileWriter fw = new FileWriter(ensembleFile, true);
2111               BufferedWriter bw = new BufferedWriter(fw);
2112               bw.write(format("MODEL        %d", evaluatedPermutations));
2113               for (int j = 0; j < 75; j++) {
2114                 bw.write(" ");
2115               }
2116               bw.newLine();
2117               bw.flush();
2118               ensembleFilter.writeFile(ensembleFile, true);
2119               bw.write("ENDMDL");
2120               for (int j = 0; j < 64; j++) {
2121                 bw.write(" ");
2122               }
2123               bw.newLine();
2124               bw.close();
2125             } catch (IOException e) {
2126               logger.warning(format("Exception writing to file: %s", ensembleFile.getName()));
2127             }
2128           }
2129           ResidueState[] states = ResidueState.storeAllCoordinates(residues);
2130           ensembleStates.add(new ObjectPair<>(states, comparisonEnergy));
2131         }
2132 
2133         if (comparisonEnergy < currentEnergy) {
2134           currentEnergy = comparisonEnergy;
2135         }
2136 
2137         if (comparisonEnergy < lowEnergy) {
2138           lowEnergy = comparisonEnergy;
2139           optimum[i] = ri;
2140         }
2141 
2142         if (useForceFieldEnergy) {
2143           // Log current results
2144           logIfRank0(format(" %6e AMOEBA: %12.4f 3-Body: %12.4f Neglected: %12.4f (%12.4f)",
2145               (double) evaluatedPermutations, comparisonEnergy, approximateEnergy,
2146               comparisonEnergy - approximateEnergy, lowEnergy));
2147         } else {
2148           logIfRank0(
2149               format(" %12s %25f %25f", evaluatedPermutations, approximateEnergy, lowEnergy));
2150         }
2151       }
2152 
2153       ensembleStates.sort(null);
2154     }
2155     return currentEnergy;
2156   }
2157 
2158   /**
2159    * A global optimization over side-chain rotamers using a recursive algorithm and information about
2160    * eliminated rotamers, rotamer pairs and rotamer triples.
2161    *
2162    * @param residues        Residue array.
2163    * @param i               Current number of permutations.
2164    * @param currentRotamers Current rotamer list.
2165    * @return 0.
2166    */
2167   private double dryRun(Residue[] residues, int i, int[] currentRotamers) {
2168     // This is the initialization condition.
2169     if (i == 0) {
2170       evaluatedPermutations = 0;
2171       evaluatedPermutationsPrint = 1000;
2172     }
2173 
2174     if (evaluatedPermutations >= evaluatedPermutationsPrint) {
2175       if (evaluatedPermutations % evaluatedPermutationsPrint == 0) {
2176         logIfRank0(
2177             format(" The permutations have reached %10.4e.", (double) evaluatedPermutationsPrint));
2178         evaluatedPermutationsPrint *= 10;
2179       }
2180     }
2181 
2182     int nResidues = residues.length;
2183     Residue residuei = residues[i];
2184     Rotamer[] rotamersi = residuei.getRotamers();
2185     int lenri = rotamersi.length;
2186     if (i < nResidues - 1) {
2187       for (int ri = 0; ri < lenri; ri++) {
2188         if (eR.check(i, ri)) {
2189           continue;
2190         }
2191         boolean deadEnd = false;
2192         for (int j = 0; j < i; j++) {
2193           int rj = currentRotamers[j];
2194           deadEnd = eR.check(j, rj, i, ri);
2195           if (deadEnd) {
2196             break;
2197           }
2198         }
2199         if (deadEnd) {
2200           continue;
2201         }
2202         currentRotamers[i] = ri;
2203         dryRun(residues, i + 1, currentRotamers);
2204       }
2205     } else {
2206       // At the end of the recursion, check each rotamer of the final residue.
2207       for (int ri = 0; ri < lenri; ri++) {
2208         if (eR.check(i, ri)) {
2209           continue;
2210         }
2211         currentRotamers[i] = ri;
2212         boolean deadEnd = false;
2213         for (int j = 0; j < i; j++) {
2214           int rj = currentRotamers[j];
2215           deadEnd = eR.check(j, rj, i, ri);
2216           if (deadEnd) {
2217             break;
2218           }
2219         }
2220         if (!deadEnd) {
2221           evaluatedPermutations++;
2222         }
2223       }
2224     }
2225 
2226     return 0.0;
2227   }
2228 
2229   /**
2230    * Finds all permutations within buffer energy of GMEC.
2231    *
2232    * @param residues            Array of residues.
2233    * @param i                   Current depth in residue/rotamer tree.
2234    * @param currentRotamers     Current set of rotamers at this node.
2235    * @param gmecEnergy          Minimum energy for these residues.
2236    * @param permutationEnergies Energy of all permutations.
2237    * @param permutations        Contains accepted permutations.
2238    * @return 0.
2239    */
2240   private double dryRunForEnsemble(Residue[] residues, int i, int[] currentRotamers,
2241                                    double gmecEnergy, double[] permutationEnergies, int[][] permutations) {
2242     // This is the initialization condition.
2243     if (i == 0) {
2244       evaluatedPermutations = 0;
2245     }
2246     int nResidues = residues.length;
2247     Residue residuei = residues[i];
2248     Rotamer[] rotamersi = residuei.getRotamers();
2249     int lenri = rotamersi.length;
2250     if (i < nResidues - 1) {
2251       for (int ri = 0; ri < lenri; ri++) {
2252         if (eR.check(i, ri)) {
2253           continue;
2254         }
2255         boolean deadEnd = false;
2256         for (int j = 0; j < i; j++) {
2257           int rj = currentRotamers[j];
2258           deadEnd = eR.check(j, rj, i, ri);
2259           if (deadEnd) {
2260             break;
2261           }
2262         }
2263         if (deadEnd) {
2264           continue;
2265         }
2266         currentRotamers[i] = ri;
2267         dryRunForEnsemble(residues, i + 1, currentRotamers, gmecEnergy, permutationEnergies,
2268             permutations);
2269       }
2270     } else {
2271       // At the end of the recursion, check each rotamer of the final residue.
2272       for (int ri = 0; ri < lenri; ri++) {
2273         if (eR.check(i, ri)) {
2274           continue;
2275         }
2276         currentRotamers[i] = ri;
2277         boolean deadEnd = false;
2278         for (int j = 0; j < i; j++) {
2279           int rj = currentRotamers[j];
2280           deadEnd = eR.check(j, rj, i, ri);
2281           if (deadEnd) {
2282             break;
2283           }
2284         }
2285         if (deadEnd) {
2286           continue;
2287         }
2288         if (permutationEnergies[evaluatedPermutations] - gmecEnergy < ensembleEnergy) {
2289           permutations[evaluatedPermutations] = new int[nResidues];
2290           arraycopy(currentRotamers, 0, permutations[evaluatedPermutations], 0, nResidues);
2291         }
2292         evaluatedPermutations++;
2293       }
2294     }
2295     return 0.0;
2296   }
2297 
2298   /**
2299    * A global optimization over side-chain rotamers using a recursive algorithm and information about
2300    * eliminated rotamers, rotamer pairs and rotamer triples.
2301    *
2302    * @param residues        Residue array.
2303    * @param i               Current number of permutations.
2304    * @param currentRotamers Current rotamer list.
2305    */
2306   public void partitionFunction(Residue[] residues, int i, int[] currentRotamers) throws Exception {
2307     // This is the initialization condition.
2308     double LOG10 = log(10.0);
2309     double temperature = 298.15;
2310     if (i == 0) {
2311       totalBoltzmann = 0;
2312       evaluatedPermutations = 0;
2313       evaluatedPermutationsPrint = 1000;
2314     }
2315 
2316     if (evaluatedPermutations >= evaluatedPermutationsPrint) {
2317       if (evaluatedPermutations % evaluatedPermutationsPrint == 0) {
2318         logIfRank0(
2319             format(" The permutations have reached %10.4e.", (double) evaluatedPermutationsPrint));
2320         evaluatedPermutationsPrint *= 10;
2321       }
2322     }
2323 
2324     int nResidues = residues.length;
2325     Residue residuei = residues[i];
2326     Rotamer[] rotamersi = residuei.getRotamers();
2327     int lenri = rotamersi.length;
2328     if (i < nResidues - 1) {
2329       for (int ri = 0; ri < lenri; ri++) {
2330         if (eR.check(i, ri)) {
2331           continue;
2332         }
2333         boolean deadEnd = false;
2334         for (int j = 0; j < i; j++) {
2335           int rj = currentRotamers[j];
2336           deadEnd = eR.check(j, rj, i, ri);
2337           if (deadEnd) {
2338             break;
2339           }
2340         }
2341         if (deadEnd) {
2342           continue;
2343         }
2344         currentRotamers[i] = ri;
2345         partitionFunction(residues, i + 1, currentRotamers);
2346       }
2347     } else {
2348       // At the end of the recursion, check each rotamer of the final residue.
2349       for (int ri = 0; ri < lenri; ri++) {
2350         if (eR.check(i, ri)) {
2351           continue;
2352         }
2353         currentRotamers[i] = ri;
2354         boolean deadEnd = false;
2355         for (int j = 0; j < i; j++) {
2356           int rj = currentRotamers[j];
2357           deadEnd = eR.check(j, rj, i, ri);
2358           if (deadEnd) {
2359             break;
2360           }
2361         }
2362         if (!deadEnd) {
2363           evaluatedPermutations++;
2364 
2365           energyRegion.init(eE, residues, currentRotamers, threeBodyTerm);
2366           parallelTeam.execute(energyRegion);
2367           double selfEnergy = energyRegion.getSelf();
2368           // Recompute the self energy from a restart file run at pH 7.0
2369           if (recomputeSelf) {
2370             int count = 0;
2371             for (Residue residue : residues) {
2372               double bias7 = 0;
2373               double biasCurrent = 0;
2374               Rotamer[] rotamers = residue.getRotamers();
2375               int currentRotamer = currentRotamers[count];
2376               switch (rotamers[currentRotamer].getName()) {
2377                 case "HIE" -> {
2378                   bias7 = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.HIStoHIE.pKa - 7)) -
2379                       TitrationUtils.Titration.HIStoHIE.freeEnergyDiff;
2380                   biasCurrent = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.HIStoHIE.pKa - pH)) -
2381                       TitrationUtils.Titration.HIStoHIE.freeEnergyDiff;
2382                 }
2383                 case "HID" -> {
2384                   bias7 = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.HIStoHID.pKa - 7)) -
2385                       TitrationUtils.Titration.HIStoHID.freeEnergyDiff;
2386                   biasCurrent = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.HIStoHID.pKa - pH)) -
2387                       TitrationUtils.Titration.HIStoHID.freeEnergyDiff;
2388                 }
2389                 case "ASP" -> {
2390                   bias7 = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.ASHtoASP.pKa - 7)) -
2391                       TitrationUtils.Titration.ASHtoASP.freeEnergyDiff;
2392                   biasCurrent = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.ASHtoASP.pKa - pH)) -
2393                       TitrationUtils.Titration.ASHtoASP.freeEnergyDiff;
2394                 }
2395                 case "GLU" -> {
2396                   bias7 = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.GLHtoGLU.pKa - 7)) -
2397                       TitrationUtils.Titration.GLHtoGLU.freeEnergyDiff;
2398                   biasCurrent = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.GLHtoGLU.pKa - pH)) -
2399                       TitrationUtils.Titration.GLHtoGLU.freeEnergyDiff;
2400                 }
2401                 case "LYD" -> {
2402                   bias7 = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.LYStoLYD.pKa - 7)) -
2403                       TitrationUtils.Titration.LYStoLYD.freeEnergyDiff;
2404                   biasCurrent = (LOG10 * Constants.R * temperature * (TitrationUtils.Titration.LYStoLYD.pKa - pH)) -
2405                       TitrationUtils.Titration.LYStoLYD.freeEnergyDiff;
2406                 }
2407                 default -> {
2408                 }
2409               }
2410               selfEnergy = selfEnergy - bias7 + biasCurrent;
2411               count += 1;
2412             }
2413           }
2414 
2415           // Calculate the total energy of a permutation/conformation
2416           double totalEnergy = eE.getBackboneEnergy() + selfEnergy +
2417               energyRegion.getTwoBody() + energyRegion.getThreeBody();
2418 
2419           // Set a reference energy to evaluate all follow energies against for the Boltzmann calculations to avoid Nan/Inf errors
2420           if (evaluatedPermutations == 1) {
2421             refEnergy = totalEnergy;
2422           }
2423           double boltzmannWeight = Math.exp((-1.0 / (Constants.kB * 298.15)) * (totalEnergy - refEnergy));
2424 
2425                     // Collect Boltzmann weight for every rotamer for residues included in the optimization
2426                     for (int res=0; res < residues.length; res++) {
2427                         int currentRotamer = currentRotamers[res];
2428                         populationBoltzmann[res][currentRotamer] += boltzmannWeight;
2429                     }
2430 
2431           // Sum Boltzmann of all permutations
2432           totalBoltzmann += boltzmannWeight;
2433         }
2434       }
2435     }
2436   }
2437 
2438   /**
2439    * Return reference energy for partition function boltzmann weights
2440    *
2441    * @return ref energy
2442    */
2443   public double getRefEnergy() {
2444     return refEnergy;
2445   }
2446 
2447   /**
2448    * Return the total boltzmann weight for an ensemble
2449    *
2450    * @return total boltzmann
2451    */
2452   public double getTotalBoltzmann() {
2453     return totalBoltzmann;
2454   }
2455 
2456   /**
2457    * Return the ensemble average of protonated rotamers for all titratable sites
2458    *
2459    * @return fraction of protonated residues
2460    */
2461   public double[][] getFraction() {
2462     return fraction;
2463   }
2464 
2465   /**
2466    * Return the Protonated Boltzmann for all sites
2467    *
2468    * @return fraction of protonated residues
2469    */
2470   public double[][] getPopulationBoltzmann() {
2471     return populationBoltzmann;
2472   }
2473 
2474   /**
2475    * Calculate population of each rotamer for residues in the system
2476    *
2477    * @param residues        residue array
2478    * @param i               int
2479    * @param currentRotamers empty array
2480    * @throws Exception too many permutations to continue
2481    */
2482   public void getFractions(Residue[] residues, int i, int[] currentRotamers) throws Exception {
2483     populationBoltzmann = new double[residues.length][56];
2484     if(!usingBoxOptimization){
2485       partitionFunction(residues, i, currentRotamers);
2486       optimum = new int[residues.length];
2487       for (int m = 0; m < fraction.length; m++) {
2488         for (int n = 0; n < 56; n++) {
2489           fraction[m][n] = populationBoltzmann[m][n] / totalBoltzmann;
2490           if(n > 0 && fraction[m][n] > fraction[m][n-1]){
2491             optimum[m] = n;
2492           } else if(n == 0){
2493             optimum[m] = n;
2494           }
2495         }
2496         Rotamer highestPopRot = residues[m].getRotamers()[optimum[m]];
2497         RotamerLibrary.applyRotamer(residues[m],highestPopRot);
2498       }
2499     }
2500       logger.info("\n   Total permutations evaluated: " + evaluatedPermutations + "\n");
2501     }
2502 
2503     /**
2504      * Return population of each rotamer for residues in the system
2505      *
2506      * @param residues        residue array
2507      * @param i               int
2508      * @param currentRotamers empty array
2509      * @throws Exception too many permutations to continue
2510      */
2511     public void getFractions(Residue[] residues, int i, int[] currentRotamers, boolean usingBoxOptimization) throws Exception {
2512         double [][] fractionSubset = new double[residues.length][56];
2513         populationBoltzmann = new double[residues.length][56];
2514         partitionFunction(residues, i, currentRotamers);
2515         optimumSubset = new int[residues.length];
2516         for (int m = 0; m < fractionSubset.length; m++) {
2517             for (int n = 0; n < 56; n++) {
2518                 fractionSubset[m][n] = populationBoltzmann[m][n] / totalBoltzmann;
2519                 if(n > 0 && fractionSubset[m][n] > fractionSubset[m][n-1]){
2520                     optimumSubset[m] = n;
2521                 } else if(n == 0){
2522                     optimumSubset[m] = n;
2523                 }
2524             }
2525             Rotamer highestPopRot = residues[m].getRotamers()[optimumSubset[m]];
2526             RotamerLibrary.applyRotamer(residues[m],highestPopRot);
2527             Residue residue = residues[m];
2528             int index = residueList.indexOf(residue);
2529             fraction[index] = fractionSubset[m];
2530             optimum[index] = optimumSubset[m];
2531         }
2532         logger.info("\n   Total permutations evaluated: " + evaluatedPermutations + "\n");
2533     }
2534 
2535     /**
2536      * Return the populations of the titratable residue states and print
2537      * @param residues residues in the system
2538      * @return double array of populations
2539      */
2540     public double[][] getProtonationPopulations(Residue[] residues){
2541         double[][] populations = new double[residues.length][3];
2542         int residueIndex = 0;
2543         for (Residue residue : residues) {
2544             int index = residueList.indexOf(residue);
2545             // Set sums for to protonated, deprotonated, and tautomer states of titratable residues
2546             double protSum = 0;
2547             double deprotSum = 0;
2548             double tautomerSum = 0;
2549             Rotamer[] rotamers = residue.getRotamers();
2550             for (int rotIndex=0; rotIndex < rotamers.length; rotIndex++) {
2551                     switch (rotamers[rotIndex].getName()) {
2552                         case "HIS":
2553                         case "LYS":
2554                         case "GLH":
2555                         case "ASH":
2556                         case "CYS":
2557                             protSum += fraction[index][rotIndex];
2558                             populations[residueIndex][0] = protSum;
2559                             break;
2560                         case "HIE":
2561                         case "LYD":
2562                         case "GLU":
2563                         case "ASP":
2564                         case "CYD":
2565                             deprotSum += fraction[index][rotIndex];
2566                             populations[residueIndex][1] = deprotSum;
2567                             break;
2568                         case "HID":
2569                             tautomerSum += fraction[index][rotIndex];
2570                             populations[residueIndex][2] = tautomerSum;
2571                             break;
2572                         default:
2573                             break;
2574                     }
2575                 }
2576             String formatedProtSum = format("%.6f", protSum);
2577             String formatedDeprotSum = format("%.6f", deprotSum);
2578             String formatedTautomerSum = format("%.6f", tautomerSum);
2579             switch (residue.getName()) {
2580                 case "HIS":
2581                 case "HIE":
2582                 case "HID":
2583                     logger.info(residue.getResidueNumber() + "\tHIS" + "\t" + formatedProtSum + "\t" +
2584                             "HIE" + "\t" + formatedDeprotSum + "\t" +
2585                             "HID" + "\t" + formatedTautomerSum);
2586                     break;
2587                 case "LYS":
2588                 case "LYD":
2589                     logger.info(residue.getResidueNumber() + "\tLYS" + "\t" + formatedProtSum + "\t" +
2590                             "LYD" + "\t" + formatedDeprotSum);
2591                     break;
2592                 case "ASH":
2593                 case "ASP":
2594                     logger.info(residue.getResidueNumber() + "\tASP" + "\t" + formatedDeprotSum + "\t" +
2595                             "ASH" + "\t" + formatedProtSum);
2596                     break;
2597                 case "GLH":
2598                 case "GLU":
2599                     logger.info(residue.getResidueNumber() + "\tGLU" + "\t" + formatedDeprotSum + "\t" +
2600                             "GLH" + "\t" + formatedProtSum);
2601                     break;
2602                 case "CYS":
2603                 case "CYD":
2604                     logger.info(residue.getResidueNumber() + "\tCYS" + "\t" + formatedProtSum + "\t" +
2605                             "CYD" + "\t" + formatedDeprotSum);
2606                     break;
2607                 default:
2608                     break;
2609             }
2610                 residueIndex++;
2611             }
2612 
2613 
2614         return populations;
2615     }
2616 
2617     public double slidingWindowCentered(List<Residue> residueList) throws Exception {
2618         String[] titratableResidues = new String[]{"HIS", "HIE", "HID", "GLU", "GLH", "ASP", "ASH", "LYS", "LYD", "CYS", "CYD"};
2619         List<String> titratableResiudesList = Arrays.asList(titratableResidues);
2620         // TO DO make generic for a list of given residue centers, make array that is filled with TR or take array of centers
2621         double e = 0.0;
2622         for (Residue titrationResidue : residueList) {
2623             if (titratableResiudesList.contains(titrationResidue.getName())) {
2624                 e = slidingWindowOptimization(residueList, windowSize, increment, revert, distance, Direction.FORWARD, residueList.indexOf(titrationResidue));
2625             }
2626         }
2627         return e;
2628     }
2629 
2630     /**
2631      * Return the rotamer index for each conformer (A,B,C) in xray and realspace genZ
2632      * @return int array of rotamer indexes for each conformer (A,B,C)
2633      * @throws Exception
2634      */
2635     public int[][] getConformers() throws Exception{
2636         int[][] conformers = new int[fraction.length][3];
2637         for(int i = 0; i < fraction.length; i++){
2638             double[] tempArray = new double[fraction[0].length];
2639             java.lang.System.arraycopy(fraction[i], 0, tempArray, 0, fraction[0].length);
2640             List<Double> elements = new ArrayList<Double>();
2641             for (int j = 0; j < tempArray.length; j++) {
2642                 elements.add(tempArray[j]);
2643             }
2644             Arrays.sort(tempArray);
2645             int count = -1;
2646             for(int k = tempArray.length-3; k < tempArray.length; k++){
2647                 count++;
2648                 conformers[i][count] = elements.indexOf(tempArray[k]);
2649             }
2650         }
2651         return conformers;
2652     }
2653 
2654 
2655   /**
2656    * Return an integer array of optimized rotamers following rotamer optimization.
2657    *
2658    * @return The optimal rotamer array.
2659    */
2660   public int[] getOptimumRotamers() {
2661     return optimum;
2662   }
2663 
2664   /**
2665    * Independent optimization treats each residue sequentially.
2666    *
2667    * @param residues The list of residues to be optimized.
2668    * @return The final energy.
2669    */
2670   private double independent(List<Residue> residues) {
2671     double e = 0.0;
2672     List<Residue> singletonResidue = new ArrayList<>(Collections.nCopies(1, null));
2673     for (int i = 0; i < residues.size(); i++) {
2674       Residue residue = residues.get(i);
2675       singletonResidue.set(0, residue);
2676       logger.info(format(" Optimizing %s side-chain.", residue));
2677       Rotamer[] rotamers = residue.getRotamers();
2678       e = Double.MAX_VALUE;
2679       int bestRotamer = 0;
2680       double startingEnergy = 0.0;
2681       for (int j = 0; j < rotamers.length; j++) {
2682         Rotamer rotamer = rotamers[j];
2683         RotamerLibrary.applyRotamer(residue, rotamer);
2684         if (algorithmListener != null) {
2685           algorithmListener.algorithmUpdate(molecularAssembly);
2686         }
2687         double newE = Double.NaN;
2688         try {
2689           if (rotamer.isTitrating) {
2690             newE = currentEnergy(singletonResidue) + rotamer.getRotamerPhBias();
2691           } else {
2692             newE = currentEnergy(singletonResidue);
2693           }
2694           // Report energies relative to the first rotamer.
2695           if (j == 0) {
2696             startingEnergy = newE;
2697             newE = 0.0;
2698           } else {
2699             newE -= startingEnergy;
2700           }
2701           logger.info(format("  Energy %8s %-2d: %s", residue.toString(rotamers[j]), j, formatEnergy(newE)));
2702           double singularityThreshold = -100000;
2703           if (newE < singularityThreshold) {
2704             String message = format("   Rejecting as energy (%s << %s) is likely an error.", formatEnergy(newE), formatEnergy(singularityThreshold));
2705             logger.info(message);
2706             newE = Double.MAX_VALUE;
2707           }
2708         } catch (ArithmeticException ex) {
2709           logger.info(format(" Exception %s in energy calculations during independent for %s-%d", ex, residue, j));
2710         }
2711         if (newE < e) {
2712           e = newE;
2713           bestRotamer = j;
2714         }
2715       }
2716       Rotamer rotamer = rotamers[bestRotamer];
2717       RotamerLibrary.applyRotamer(residue, rotamer);
2718       optimum[i] = bestRotamer;
2719       logger.info(format(" Best Energy %8s %-2d: %s", residue.toString(rotamer), bestRotamer, formatEnergy(e)));
2720 
2721       if (algorithmListener != null) {
2722         algorithmListener.algorithmUpdate(molecularAssembly);
2723       }
2724       if (terminate) {
2725         logger.info(format("\n Terminating after residue %s.\n", residue));
2726         break;
2727       }
2728     }
2729     return e;
2730   }
2731 
2732   /**
2733    * Finds the first non-eliminated rotamer permutation.
2734    *
2735    * @param residues        Array of residues.
2736    * @param i               Current permutation.
2737    * @param currentRotamers Current list of rotamers.
2738    * @return If valid permutation found.
2739    */
2740   private boolean firstValidPerm(Residue[] residues, int i, int[] currentRotamers) {
2741     int nResidues = residues.length;
2742     Residue residuei = residues[i];
2743     Rotamer[] rotamersi = residuei.getRotamers();
2744     int lenri = rotamersi.length;
2745     if (i < nResidues - 1) {
2746       for (int ri = 0; ri < lenri; ri++) {
2747         if (eR.check(i, ri)) {
2748           continue;
2749         }
2750         boolean deadEnd = false;
2751         for (int j = 0; j < i; j++) {
2752           int rj = currentRotamers[j];
2753           deadEnd = eR.check(j, rj, i, ri);
2754           if (deadEnd) {
2755             break;
2756           }
2757         }
2758         if (deadEnd) {
2759           continue;
2760         }
2761         currentRotamers[i] = ri;
2762         if (firstValidPerm(residues, i + 1, currentRotamers)) {
2763           return true;
2764         }
2765       }
2766     } else {
2767       // At the end of the recursion, check each rotamer of the final residue.
2768       for (int ri = 0; ri < lenri; ri++) {
2769         if (eR.check(i, ri)) {
2770           continue;
2771         }
2772         currentRotamers[i] = ri;
2773         boolean deadEnd = false;
2774         for (int j = 0; j < i; j++) {
2775           int rj = currentRotamers[j];
2776           deadEnd = eR.check(j, rj, i, ri);
2777           if (deadEnd) {
2778             break;
2779           }
2780         }
2781         if (!deadEnd) {
2782           break;
2783         }
2784       }
2785       return true;
2786     }
2787     return false;
2788   }
2789 
2790   /**
2791    * The main driver for optimizing a block of residues using DEE.
2792    *
2793    * @param residueList Residues to optimize.
2794    * @return Final energy.
2795    */
2796   protected double globalOptimization(List<Residue> residueList) {
2797     int currentEnsemble = Integer.MAX_VALUE;
2798     Residue[] residues = residueList.toArray(new Residue[0]);
2799     int nResidues = residues.length;
2800     int[] currentRotamers = new int[nResidues];
2801     optimumSubset = new int[nResidues];
2802 
2803     int iterations = 0;
2804     boolean finalTry = false;
2805     int bestEnsembleTargetDiffThusFar = Integer.MAX_VALUE;
2806     double bestBufferThusFar = ensembleBuffer;
2807     double startingBuffer = ensembleBuffer;
2808 
2809     if (ensembleEnergy > 0.0) {
2810       ensembleBuffer = ensembleEnergy;
2811       applyEliminationCriteria(residues, true, true);
2812       // Compute the number of permutations without eliminating dead-ends and compute the number of
2813       // permutations using singleton elimination.
2814       double permutations = 1;
2815       double singletonPermutations = 1;
2816       for (int i = 0; i < nResidues; i++) {
2817         Residue residue = residues[i];
2818         Rotamer[] rotamers = residue.getRotamers();
2819         int nr = rotamers.length;
2820         if (nr > 1) {
2821           int nrot = 0;
2822           for (int ri = 0; ri < nr; ri++) {
2823             if (!eR.eliminatedSingles[i][ri]) {
2824               nrot++;
2825             }
2826           }
2827           permutations *= rotamers.length;
2828           if (nrot > 1) {
2829             singletonPermutations *= nrot;
2830           }
2831         }
2832       }
2833       dryRun(residues, 0, currentRotamers);
2834       double pairTotalElimination = singletonPermutations - (double) evaluatedPermutations;
2835       double afterPairElim = singletonPermutations - pairTotalElimination;
2836       if (evaluatedPermutations == 0) {
2837         logger.severe(
2838             " No valid path through rotamer space found; try recomputing without pruning or using ensemble.");
2839       }
2840       if (rank0 && printFiles && ensembleFile == null) {
2841         File file = molecularAssembly.getFile();
2842         String filename = FilenameUtils.removeExtension(file.getAbsolutePath());
2843         ensembleFile = new File(filename + ".ens");
2844         if (ensembleFile.exists()) {
2845           for (int i = 2; i < 1000; i++) {
2846             ensembleFile = new File(filename + ".ens_" + i);
2847             if (!ensembleFile.exists()) {
2848               break;
2849             }
2850           }
2851           if (ensembleFile.exists()) {
2852             logger.warning(
2853                 format(" Versioning failed: appending to end of file %s", ensembleFile.getName()));
2854           }
2855         }
2856         ensembleFilter = new PDBFilter(new File(ensembleFile.getName()), molecularAssembly, null,
2857             null);
2858         logger.info(format(" Ensemble file: %s", ensembleFile.getName()));
2859       }
2860       logIfRank0(format("%30s %35s %35s", "Condition", "Number of Permutations Left",
2861           "Number of Permutations Removed"));
2862       logIfRank0(format("%30s %35s %35s", "No Eliminations", permutations, ""));
2863       logIfRank0(format("%30s %35s %35s", "Single Eliminations", singletonPermutations,
2864           permutations - singletonPermutations));
2865       logIfRank0(
2866           format("%30s %35s %35s", "Pair Eliminations", afterPairElim, pairTotalElimination));
2867       logIfRank0(
2868           format("%30s %35s %35s", "Single and Pair Eliminations", (double) evaluatedPermutations,
2869               pairTotalElimination + (permutations - singletonPermutations)));
2870 
2871       logIfRank0("\n Energy of permutations:");
2872       logIfRank0(format(" %12s %25s %25s", "Permutation", "Energy", "Lowest Possible Energy"));
2873 
2874       double e;
2875       if (useMonteCarlo()) {
2876         firstValidPerm(residues, 0, currentRotamers);
2877         arraycopy(currentRotamers, 0, optimumSubset, 0, nResidues);
2878         rotamerOptimizationMC(residues, optimumSubset, currentRotamers, nMCSteps, false, mcUseAll);
2879 
2880         logIfRank0(" Ensembles not currently compatible with Monte Carlo search");
2881         // Not currently compatible with ensembles.
2882       } else {
2883         double[] permutationEnergies = new double[evaluatedPermutations];
2884         ensembleStates = new ArrayList<>();
2885 
2886         e = rotamerOptimizationDEE(molecularAssembly, residues, 0, currentRotamers, Double.MAX_VALUE,
2887             optimumSubset, permutationEnergies);
2888         int[][] acceptedPermutations = new int[evaluatedPermutations][];
2889         logIfRank0(format(
2890             "\n Checking permutations for distance < %5.3f kcal/mol from GMEC energy %10.8f kcal/mol",
2891             ensembleEnergy, e));
2892         dryRunForEnsemble(residues, 0, currentRotamers, e, permutationEnergies,
2893             acceptedPermutations);
2894         int numAcceptedPermutations = 0;
2895 
2896         for (int i = 0; i < acceptedPermutations.length; i++) {
2897           if (acceptedPermutations[i] != null) {
2898             ++numAcceptedPermutations;
2899             logIfRank0(
2900                 format(" Accepting permutation %d at %8.6f < %8.6f", i, permutationEnergies[i] - e,
2901                     ensembleEnergy));
2902             for (int j = 0; j < nResidues; j++) {
2903               Residue residuej = residues[j];
2904               Rotamer[] rotamersj = residuej.getRotamers();
2905               RotamerLibrary.applyRotamer(residuej, rotamersj[acceptedPermutations[i][j]]);
2906             }
2907 
2908             ResidueState[] states = ResidueState.storeAllCoordinates(residues);
2909             ensembleStates.add(new ObjectPair<>(states, permutationEnergies[i]));
2910 
2911             if (printFiles && rank0) {
2912               try {
2913                 FileWriter fw = new FileWriter(ensembleFile, true);
2914                 BufferedWriter bw = new BufferedWriter(fw);
2915                 bw.write(format("MODEL        %d", numAcceptedPermutations));
2916                 for (int j = 0; j < 75; j++) {
2917                   bw.write(" ");
2918                 }
2919                 bw.newLine();
2920                 bw.flush();
2921                 ensembleFilter.writeFile(ensembleFile, true);
2922                 bw.write("ENDMDL");
2923                 for (int j = 0; j < 64; j++) {
2924                   bw.write(" ");
2925                 }
2926                 bw.newLine();
2927                 bw.close();
2928               } catch (IOException ex) {
2929                 logger.warning(format(" Exception writing to file: %s", ensembleFile.getName()));
2930               }
2931             }
2932           }
2933         }
2934         logIfRank0(format(" Number of permutations within %5.3f kcal/mol of GMEC energy: %6.4e",
2935             ensembleEnergy, (double) numAcceptedPermutations));
2936         ensembleStates.sort(null);
2937       }
2938 
2939       logIfRank0(" Final rotamers:");
2940       logIfRank0(format("%17s %10s %11s %12s %11s", "Residue", "Chi 1", "Chi 2", "Chi 3", "Chi 4"));
2941       for (int i = 0; i < nResidues; i++) {
2942         Residue residue = residues[i];
2943         Rotamer[] rotamers = residue.getRotamers();
2944         int ri = optimumSubset[i];
2945         Rotamer rotamer = rotamers[ri];
2946         logIfRank0(format(" %c (%7s,%2d) %s", residue.getChainID(), residue.toString(rotamer), ri,
2947             rotamer.toAngleString()));
2948         RotamerLibrary.applyRotamer(residue, rotamer);
2949       }
2950       logIfRank0("\n");
2951 
2952       double sumSelfEnergy = 0;
2953       double sumPairEnergy = 0;
2954       double sumTrimerEnergy = 0;
2955       for (int i = 0; i < nResidues; i++) {
2956         Residue residue = residues[i];
2957         Rotamer[] rotamers = residue.getRotamers();
2958         int ri = optimumSubset[i];
2959         sumSelfEnergy += eE.getSelf(i, ri);
2960         logIfRank0(
2961             format(" Final self Energy (%8s,%2d): %12.4f", residue.toString(rotamers[ri]), ri,
2962                 eE.getSelf(i, ri)));
2963       }
2964       for (int i = 0; i < nResidues - 1; i++) {
2965         Residue residueI = residues[i];
2966         Rotamer[] rotI = residueI.getRotamers();
2967         int ri = optimumSubset[i];
2968         for (int j = i + 1; j < nResidues; j++) {
2969           Residue residueJ = residues[j];
2970           Rotamer[] rotJ = residueJ.getRotamers();
2971           int rj = optimumSubset[j];
2972           sumPairEnergy += eE.get2Body(i, ri, j, rj);
2973           if (eE.get2Body(i, ri, j, rj) > 10.0) {
2974             logIfRank0(format(" Large Final Pair Energy (%8s,%2d) (%8s,%2d): %12.4f",
2975                 residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj,
2976                 eE.get2Body(i, ri, j, rj)));
2977           }
2978         }
2979       }
2980 
2981       try {
2982         // Add the force field energy to the pH bias.
2983         e = currentEnergy(residueList) + eE.getTotalRotamerPhBias(residueList, optimumSubset, pH, pHRestraint);
2984       } catch (ArithmeticException ex) {
2985         e = Double.NaN;
2986         logger.severe(
2987             format(" Exception %s in calculating current energy at the end of triples", ex));
2988       }
2989 
2990       logIfRank0(format(" %12s %25s %25s", "Type", "Energy", "Lowest Possible Energy"));
2991       logIfRank0(format(" %12s %25f %25s", "Self:", sumSelfEnergy, ""));
2992       logIfRank0(format(" %12s %25f %25s", "Pair:", sumPairEnergy, ""));
2993 
2994       approximateEnergy = eE.getBackboneEnergy() + sumSelfEnergy + sumPairEnergy;
2995 
2996       if (threeBodyTerm) {
2997         for (int i = 0; i < nResidues - 2; i++) {
2998           int ri = optimumSubset[i];
2999           for (int j = i + 1; j < nResidues - 1; j++) {
3000             int rj = optimumSubset[j];
3001             for (int k = j + 1; k < nResidues; k++) {
3002               int rk = optimumSubset[k];
3003               try {
3004                 sumTrimerEnergy += eE.get3Body(residues, i, ri, j, rj, k, rk);
3005               } catch (Exception ex) {
3006                 logger.warning(ex.toString());
3007               }
3008             }
3009           }
3010         }
3011         approximateEnergy += sumTrimerEnergy;
3012         double higherOrderEnergy =
3013             e - sumSelfEnergy - sumPairEnergy - sumTrimerEnergy - eE.getBackboneEnergy();
3014         logIfRank0(format(" %12s %25f %25s", "Trimer:", sumTrimerEnergy, ""));
3015         logIfRank0(format(" %12s %25f %25s", "Neglected:", higherOrderEnergy, ""));
3016       } else {
3017         double higherOrderEnergy = e - sumSelfEnergy - sumPairEnergy - eE.getBackboneEnergy();
3018         logIfRank0(format(" %12s %25f %25s", "Neglected:", higherOrderEnergy, ""));
3019       }
3020       logIfRank0(format(" %12s %25f %25s", "Approximate:", approximateEnergy, ""));
3021       return e;
3022     }
3023 
3024     // Permutations used only to set maximum bound on ensembleNumber, thus it is safe here to put
3025     // that value in a 32-bit int.
3026     int nPerms = 1;
3027     for (Residue residue : residues) {
3028       Rotamer[] rotamers = residue.getRotamers();
3029       int nr = rotamers.length;
3030       if (nr > 1) {
3031         nPerms *= rotamers.length;
3032       }
3033       if (nPerms > ensembleNumber) {
3034         break;
3035       }
3036     }
3037 
3038     if (nPerms < ensembleNumber) {
3039       logger.info(format(
3040           " Requested an ensemble of %d, but only %d permutations exist; returning full ensemble",
3041           ensembleNumber, nPerms));
3042       ensembleNumber = nPerms;
3043     }
3044 
3045     while (currentEnsemble != ensembleNumber) {
3046       if (monteCarlo) {
3047         logIfRank0(" Ensemble search not currently compatible with Monte Carlo");
3048         ensembleNumber = 1;
3049       }
3050 
3051       if (iterations == 0) {
3052         applyEliminationCriteria(residues, true, true);
3053       } else {
3054         applyEliminationCriteria(residues, false, false);
3055       }
3056 
3057       // Compute the number of permutations without eliminating dead-ends and compute the number of
3058       // permutations using singleton elimination.
3059       double permutations = 1;
3060       double singletonPermutations = 1;
3061       for (int i = 0; i < nResidues; i++) {
3062         Residue residue = residues[i];
3063         Rotamer[] rotamers = residue.getRotamers();
3064         int nr = rotamers.length;
3065         if (nr > 1) {
3066           int nrot = 0;
3067           for (int ri = 0; ri < nr; ri++) {
3068             if (!eR.eliminatedSingles[i][ri]) {
3069               nrot++;
3070             }
3071           }
3072           permutations *= rotamers.length;
3073           if (nrot > 1) {
3074             singletonPermutations *= nrot;
3075           }
3076         }
3077       }
3078 
3079       logIfRank0(" Collecting Permutations:");
3080       dryRun(residues, 0, currentRotamers);
3081 
3082       double pairTotalElimination = singletonPermutations - (double) evaluatedPermutations;
3083       double afterPairElim = singletonPermutations - pairTotalElimination;
3084       currentEnsemble = evaluatedPermutations;
3085       if (ensembleNumber == 1 && currentEnsemble == 0) {
3086         logger.severe(
3087             " No valid path through rotamer space found; try recomputing without pruning or using ensemble.");
3088       }
3089       if (ensembleNumber > 1) {
3090         if (rank0 && printFiles && ensembleFile == null) {
3091           File file = molecularAssembly.getFile();
3092           String filename = FilenameUtils.removeExtension(file.getAbsolutePath());
3093           ensembleFile = new File(filename + ".ens");
3094           if (ensembleFile.exists()) {
3095             for (int i = 2; i < 1000; i++) {
3096               ensembleFile = new File(filename + ".ens_" + i);
3097               if (!ensembleFile.exists()) {
3098                 break;
3099               }
3100             }
3101             if (ensembleFile.exists()) {
3102               logger.warning(
3103                   format(" Versioning failed: appending to end of file %s", ensembleFile.getName()));
3104             }
3105           }
3106           ensembleFilter = new PDBFilter(new File(ensembleFile.getName()), molecularAssembly, null,
3107               null);
3108           logger.info(format(" Ensemble file: %s", ensembleFile.getName()));
3109         }
3110         logIfRank0(format(" Ensemble Search Stats: (buffer: %5.3f, current: %d, target: %d)",
3111             ensembleBuffer, currentEnsemble, ensembleNumber));
3112       }
3113       if (ensembleNumber == 1 || finalTry) {
3114         logIfRank0(format("%30s %35s %35s", "Condition", "Number of Permutations Left",
3115             "Number of Permutations Removed"));
3116         logIfRank0(format("%30s %35s %35s", "No Eliminations", permutations, ""));
3117         logIfRank0(format("%30s %35s %35s", "Single Eliminations", singletonPermutations,
3118             permutations - singletonPermutations));
3119         logIfRank0(
3120             format("%30s %35s %35s", "Pair Eliminations", afterPairElim, pairTotalElimination));
3121         logIfRank0(
3122             format("%30s %35s %35s", "Single and Pair Eliminations", (double) evaluatedPermutations,
3123                 pairTotalElimination + (permutations - singletonPermutations)));
3124 
3125         logIfRank0("\n Energy of permutations:");
3126         logIfRank0(format(" %12s %25s %25s", "Permutation", "Energy", "Lowest Possible Energy"));
3127 
3128         break;
3129       }
3130       if (abs(currentEnsemble - ensembleNumber) < bestEnsembleTargetDiffThusFar) {
3131         bestEnsembleTargetDiffThusFar = Math.abs(currentEnsemble - ensembleNumber);
3132         bestBufferThusFar = ensembleBuffer;
3133       }
3134       if (currentEnsemble > ensembleNumber) {
3135         ensembleBuffer -= ensembleBufferStep;
3136         ensembleBufferStep -= (ensembleBufferStep * 0.01);
3137         iterations++;
3138       } else if (currentEnsemble < ensembleNumber) {
3139         ensembleBuffer += ensembleBufferStep;
3140         ensembleBufferStep -= (ensembleBufferStep * 0.01);
3141         iterations++;
3142       }
3143       if (iterations > 100) {
3144         if (currentEnsemble == 0) {
3145           // TODO: Decide whether we like these next four lines.  Has the potential to produce a
3146           // crazy amount of permutations.
3147           logIfRank0(" Ensemble still empty; increasing buffer energy.");
3148           startingBuffer = 3 * startingBuffer;
3149           setEnsemble(10, startingBuffer);
3150           iterations = 0;
3151         } else {
3152           ensembleBuffer = bestBufferThusFar;
3153           finalTry = true;
3154         }
3155       }
3156     }
3157 
3158     if (currentEnsemble == 0) {
3159       logger.warning(
3160           " No valid rotamer permutations found; results will be unreliable.  Try increasing the starting ensemble buffer.");
3161     }
3162     double[] permutationEnergyStub = null;
3163 
3164     if (useMonteCarlo()) {
3165       firstValidPerm(residues, 0, currentRotamers);
3166       rotamerOptimizationMC(residues, optimumSubset, currentRotamers, nMCSteps, false, mcUseAll);
3167     } else {
3168       rotamerOptimizationDEE(molecularAssembly, residues, 0, currentRotamers, Double.MAX_VALUE,
3169           optimumSubset, permutationEnergyStub);
3170     }
3171 
3172     double[] residueEnergy = new double[nResidues];
3173 
3174     double sumSelfEnergy = 0;
3175     double sumLowSelfEnergy = 0;
3176 
3177     logIfRank0("\n Energy contributions:");
3178     logIfRank0(format(" %15s %25s %25s", "Type", "Energy", "Lowest Possible Energy"));
3179 
3180     for (int i = 0; i < nResidues; i++) {
3181       int ri = optimumSubset[i];
3182       Residue residue = residues[i];
3183       Rotamer[] rotamers = residue.getRotamers();
3184       turnOnAtoms(residue);
3185       RotamerLibrary.applyRotamer(residue, rotamers[ri]);
3186       double self = eE.getSelf(i, ri);
3187       residueEnergy[i] = self;
3188       sumSelfEnergy += self;
3189       double lowest = eE.lowestSelfEnergy(residues, i);
3190       sumLowSelfEnergy += lowest;
3191       if (self - lowest > 10.0) {
3192         logIfRank0(
3193             format(" %15s %25f %25f", "Self (" + residues[i] + "," + ri + "):", self, lowest));
3194       }
3195     }
3196 
3197     double sumPairEnergy = 0.0;
3198     double sumLowPairEnergy = 0.0;
3199     double[] resPairEnergy = new double[nResidues];
3200     double[] lowPairEnergy = new double[nResidues];
3201     for (int i = 0; i < nResidues - 1; i++) {
3202       StringBuilder sb = new StringBuilder();
3203       int ri = optimumSubset[i];
3204       double sumPairEnergyI = 0;
3205       double sumLowPairEnergyI = 0;
3206       for (int j = i + 1; j < nResidues; j++) {
3207         int rj = optimumSubset[j];
3208         double pair = eE.get2Body(i, ri, j, rj);
3209         residueEnergy[i] += 0.5 * pair;
3210         residueEnergy[j] += 0.5 * pair;
3211         sumPairEnergy += pair;
3212         sumPairEnergyI += pair;
3213         double lowest = eE.lowestPairEnergy(residues, i, ri, j);
3214         sumLowPairEnergy += lowest;
3215         sumLowPairEnergyI += lowest;
3216         resPairEnergy[i] = 0.5 * pair;
3217         resPairEnergy[j] = 0.5 * pair;
3218         lowPairEnergy[i] = 0.5 * lowest;
3219         lowPairEnergy[j] = 0.5 * lowest;
3220         if (resPairEnergy[i] - lowPairEnergy[i] > 10.0) {
3221           sb.append(format("  Pair Energy (%8s,%2d) (%8s,%2d): %12.4f (Lowest: %12.4f).\n",
3222               residues[i].toFormattedString(false, true), ri,
3223               residues[j].toFormattedString(false, true), rj, pair, lowest));
3224         }
3225       }
3226       if (sumPairEnergyI - sumLowPairEnergyI > 10.0) {
3227         logIfRank0(
3228             format(" %15s %25f %25f", "Self (" + residues[i] + "," + ri + ")", sumPairEnergyI,
3229                 sumLowPairEnergyI));
3230         sb.trimToSize();
3231         if (!sb.toString().isEmpty()) {
3232           logIfRank0(sb.toString());
3233         }
3234       }
3235     }
3236 
3237     double e = Double.NaN;
3238     try {
3239       // Add the force field energy to the pH bias.
3240       e = currentEnergy(residueList) + eE.getTotalRotamerPhBias(residueList, optimumSubset, pH, pHRestraint);
3241     } catch (ArithmeticException ex) {
3242       logger.severe(
3243           format(" Exception %s in calculating current energy at the end of self and pairs", ex));
3244     }
3245     logIfRank0(format(" %15s %25f %25s", "Backbone:", eE.getBackboneEnergy(), ""));
3246     logIfRank0(format(" %15s %25f %25f", "Self:", sumSelfEnergy, sumLowSelfEnergy));
3247     logIfRank0(format(" %15s %25f %25f", "Pair:", sumPairEnergy, sumLowPairEnergy));
3248 
3249     approximateEnergy = eE.getBackboneEnergy() + sumSelfEnergy + sumPairEnergy;
3250 
3251     double sumTrimerEnergy = 0;
3252     if (threeBodyTerm) {
3253       for (int i = 0; i < nResidues - 2; i++) {
3254         int ri = optimumSubset[i];
3255         for (int j = i + 1; j < nResidues - 1; j++) {
3256           int rj = optimumSubset[j];
3257           for (int k = j + 1; k < nResidues; k++) {
3258             int rk = optimumSubset[k];
3259             try {
3260               double triple = eE.get3Body(residues, i, ri, j, rj, k, rk);
3261               double thirdTrip = triple / 3.0;
3262               residueEnergy[i] += thirdTrip;
3263               residueEnergy[j] += thirdTrip;
3264               residueEnergy[k] += thirdTrip;
3265               sumTrimerEnergy += triple;
3266             } catch (Exception ex) {
3267               logger.warning(ex.toString());
3268             }
3269           }
3270         }
3271       }
3272       approximateEnergy += sumTrimerEnergy;
3273       double higherOrderEnergy =
3274           e - sumSelfEnergy - sumPairEnergy - sumTrimerEnergy - eE.getBackboneEnergy();
3275       logIfRank0(format(" %15s %25f %25s", "Trimer:", sumTrimerEnergy, ""));
3276       logIfRank0(format(" %15s %25f %25s", "Neglected:", higherOrderEnergy, ""));
3277     } else {
3278       double higherOrderEnergy = e - sumSelfEnergy - sumPairEnergy - eE.getBackboneEnergy();
3279       logIfRank0(format(" %15s %25f %25s", "Neglected:", higherOrderEnergy, ""));
3280     }
3281 
3282     logIfRank0(format(" %15s %25f %25s", "Approximate:", approximateEnergy, ""));
3283 
3284     logIfRank0("\n Final rotamers:");
3285     logIfRank0(
3286         format("%17s %10s %11s %12s %11s %14s", "Residue", "Chi 1", "Chi 2", "Chi 3", "Chi 4",
3287             "Energy"));
3288     for (int i = 0; i < nResidues; i++) {
3289       Residue residue = residues[i];
3290       Rotamer[] rotamers = residue.getRotamers();
3291       int ri = optimumSubset[i];
3292       Rotamer rotamer = rotamers[ri];
3293       logIfRank0(format(" %3d %c (%7s,%2d) %s %12.4f ", i + 1, residue.getChainID(),
3294           residue.toString(rotamers[ri]), ri, rotamer.toAngleString(), residueEnergy[i]));
3295       RotamerLibrary.applyRotamer(residue, rotamer);
3296     }
3297     logIfRank0("\n");
3298     return e;
3299   }
3300 
3301   /**
3302    * Use Monte Carlo if monteCarlo specified, and either skipDEE specified or nMCsteps is smaller
3303    * than the remaining permutation size.
3304    *
3305    * @return Finish DEE search with Monte Carlo.
3306    */
3307   private boolean useMonteCarlo() {
3308     return monteCarlo && (mcNoEnum || (nMCSteps < evaluatedPermutations));
3309   }
3310 
3311   /**
3312    * Performs a recursive brute-force rotamer optimization over a passed list of residues.
3313    *
3314    * @param residueList Residues to be optimized.
3315    * @return Global minimum energy conformation energy.
3316    */
3317   private double bruteForce(List<Residue> residueList) {
3318 
3319     Residue[] residues = residueList.toArray(new Residue[0]);
3320     int nResidues = residues.length;
3321 
3322     // Compute the number of permutations without eliminating dead-ends.
3323     double permutations = 1;
3324     for (Residue residue : residues) {
3325       Rotamer[] rotamers = residue.getRotamers();
3326       permutations *= rotamers.length;
3327     }
3328 
3329     logger.info(format(" Number of permutations: %16.8e.", permutations));
3330 
3331     double e;
3332     useForceFieldEnergy = molecularAssembly.getProperties()
3333         .getBoolean("ro-use-force-field-energy", false);
3334     if (!useForceFieldEnergy) {
3335       // Use a many-body energy sum to evaluate permutations.
3336       setPruning(0);
3337       rotamerEnergies(residues);
3338       int[] currentRotamers = new int[nResidues];
3339       e = decomposedRotamerOptimization(residues, 0, Double.MAX_VALUE, optimum, currentRotamers);
3340 
3341       // Set each residue to their optimal rotamer.
3342       for (int i = 0; i < nResidues; i++) {
3343         Residue residue = residues[i];
3344         Rotamer[] rotamers = residue.getRotamers();
3345         RotamerLibrary.applyRotamer(residue, rotamers[optimum[i]]);
3346         turnOnAtoms(residue);
3347       }
3348 
3349       // Compute the full energy for comparison (i.e., without many-body truncation).
3350       double fullEnergy = 0;
3351       try {
3352         // Add the force field energy to the pH bias.
3353         fullEnergy = currentEnergy(residueList) + eE.getTotalRotamerPhBias(residueList, optimum, pH, pHRestraint);
3354       } catch (Exception ex) {
3355         logger.severe(format(" Exception %s in calculating full energy; FFX shutting down", ex));
3356       }
3357 
3358       logger.info(format(" Final summation of energies:    %16.5f", e));
3359       logger.info(format(" Final energy of optimized structure:    %16.5f", fullEnergy));
3360       logger.info(format(" Neglected:    %16.5f", fullEnergy - e));
3361     } else {
3362       e = rotamerOptimization(molecularAssembly, residues, 0, Double.MAX_VALUE, optimum);
3363     }
3364 
3365     for (int i = 0; i < nResidues; i++) {
3366       Residue residue = residues[i];
3367       Rotamer[] rotamers = residue.getRotamers();
3368       int ri = optimum[i];
3369       Rotamer rotamer = rotamers[ri];
3370       logger.info(format(" %s %s (%d)", residue.getResidueNumber(), rotamer.toString(), ri));
3371       RotamerLibrary.applyRotamer(residue, rotamer);
3372       if (useForceFieldEnergy) {
3373         try {
3374           e = currentEnergy(residueList) + eE.getTotalRotamerPhBias(residueList, optimum, pH, pHRestraint);
3375         } catch (ArithmeticException ex) {
3376           logger.fine(
3377               format(" Exception %s in calculating full AMOEBA energy at the end of brute force",
3378                   ex));
3379         }
3380       }
3381     }
3382     return e;
3383   }
3384 
3385   @SuppressWarnings("fallthrough")
3386   private double slidingWindowOptimization(List<Residue> residueList, int windowSize, int increment,
3387                                            boolean revert, double distance, Direction direction, int windowCenter) throws Exception {
3388 
3389     long beginTime = -System.nanoTime();
3390     boolean incrementTruncated = false;
3391     boolean firstWindowSaved = false;
3392     int counter = 1;
3393     int windowEnd;
3394 
3395     int nOptimize = residueList.size();
3396     if (nOptimize < windowSize) {
3397       windowSize = nOptimize;
3398       logger.info(" Window size too small for given residue range; truncating window size.");
3399     }
3400     switch (direction) {
3401       case BACKWARD:
3402         List<Residue> temp = new ArrayList<>();
3403         for (int i = nOptimize - 1; i >= 0; i--) {
3404           temp.add(residueList.get(i));
3405         }
3406         residueList = temp;
3407         // Fall through into the FORWARD case.
3408       case FORWARD:
3409         for (int windowStart = 0; windowStart + (windowSize - 1) < nOptimize;
3410              windowStart += increment) {
3411           long windowTime = -System.nanoTime();
3412 // Set the start at the residue based on my center
3413           if(windowCenter > -1){
3414               windowStart =  windowCenter - windowSize/2;
3415               if(windowStart < 0){
3416                   windowStart = 0;
3417               }
3418               windowEnd = windowCenter + windowSize/2;
3419               if(windowEnd >= residueList.size()){
3420                   windowEnd = residueList.size() - 1;
3421               }
3422           } else {
3423               windowEnd = windowStart + (windowSize - 1);
3424           }
3425 
3426           if(windowCenter > -1){
3427               logIfRank0(format("\n Center window at residue %d.\n", residueList.get(windowCenter).getResidueNumber()));
3428           } else {
3429               logIfRank0(format("\n Iteration %d of the sliding window.\n", counter++));
3430           }
3431 
3432           Residue firstResidue = residueList.get(windowStart);
3433           Residue lastResidue = residueList.get(windowEnd);
3434           if (firstResidue != lastResidue) {
3435             logIfRank0(
3436                 format(" Residues %s ... %s", firstResidue.toString(), lastResidue.toString()));
3437           } else {
3438             logIfRank0(format(" Residue %s", firstResidue.toString()));
3439           }
3440           List<Residue> currentWindow = new ArrayList<>();
3441           for (int i = windowStart; i <= windowEnd; i++) {
3442             Residue residue = residueList.get(i);
3443             currentWindow.add(residue);
3444           }
3445 
3446           if (distance > 0) {
3447             for (int i = windowStart; i <= windowEnd; i++) {
3448               if(windowCenter > -1){
3449                   i = windowCenter;
3450               }
3451               Residue residuei = residueList.get(i);
3452               int indexI = allResiduesList.indexOf(residuei);
3453               int lengthRi;
3454               lengthRi = residuei.getRotamers().length;
3455               for (int ri = 0; ri < lengthRi; ri++) {
3456                 for (int j = 0; j < nAllResidues; j++) {
3457                   Residue residuej = allResiduesArray[j];
3458                   Rotamer[] rotamersj = residuej.getRotamers();
3459                   if (currentWindow.contains(residuej) || rotamersj == null) {
3460                     continue;
3461                   }
3462                   int lengthRj = rotamersj.length;
3463                   for (int rj = 0; rj < lengthRj; rj++) {
3464                     double rotamerSeparation = dM.get2BodyDistance(indexI, ri, j, rj);
3465                     if (rotamerSeparation <= distance) {
3466                       if (!currentWindow.contains(residuej)) {
3467                         logIfRank0(format(" Adding residue %s at distance %16.8f Ang from %s %d.",
3468                             residuej.toFormattedString(false, true), rotamerSeparation,
3469                             residuei.toFormattedString(false, true), ri));
3470                         currentWindow.add(residuej);
3471                       }
3472                       break;
3473                     }
3474                   }
3475                 }
3476               }
3477               if(windowCenter > -1){
3478                   break;
3479               }
3480             }
3481           }
3482 
3483           /*
3484            If the window starts with a nucleic acid, and there is a 5' NA residue, ensure that 5'
3485            NA residue has been included in the window. Otherwise, that previous residue may not
3486            have had a chance to be flexible about its sugar pucker.
3487            <p>
3488            If window size is greater than increment, however, this has already been handled.
3489            Additionally, do not perform this for the first window (counter is already incremented
3490            by the time this check is performed, so first window's counter will be 2). Furthermore,
3491            do not include Residues with null Rotamer lists (this breaks things).
3492            <p>
3493            The issue: if window size = increment, the last NA residue in each window will not
3494            have flexibility about its sugar pucker, because its self-energy includes the O3' (i)
3495            to P (i+1) bond, so it must remain in the original sugar pucker to meet the i+1
3496            residue. However, this problem can be solved by ensuring that final residue is included
3497            in the next window, where it will have flexibility about its sugar pucker.
3498            <p>
3499            If you are running successive sliding window jobs on the same file, I would suggest
3500            starting the next job on the last residue of the previous job, unless you know your
3501            settings will include it.
3502           */
3503           if (counter > 2 && windowSize <= increment && firstResidue.getResidueType() == NA) {
3504             Residue prevResidue = firstResidue.getPreviousResidue();
3505             if (prevResidue != null && prevResidue.getResidueType() == NA
3506                 && !currentWindow.contains(prevResidue) && prevResidue.getRotamers() != null) {
3507               logIfRank0(format(
3508                   " Adding nucleic acid residue 5' of window start %s to give it flexibility about its sugar pucker.",
3509                   prevResidue));
3510               currentWindow.add(prevResidue);
3511             }
3512           }
3513           sortResidues(currentWindow);
3514 
3515           if (revert) {
3516             ResidueState[] coordinates = ResidueState.storeAllCoordinates(currentWindow);
3517             double startingEnergy = Double.NaN;
3518             try {
3519               startingEnergy = currentEnergy(currentWindow);
3520             } catch (ArithmeticException ex) {
3521               logger.severe(format(
3522                   " Exception %s in calculating starting energy of a window; FFX shutting down",
3523                   ex));
3524             }
3525             globalOptimization(currentWindow);
3526             double finalEnergy = Double.NaN;
3527             try {
3528               finalEnergy = currentEnergy(currentWindow);
3529             } catch (ArithmeticException ex) {
3530               logger.severe(
3531                   format(" Exception %s in calculating final energy of a window; FFX shutting down",
3532                       ex));
3533             }
3534             if (startingEnergy <= finalEnergy) {
3535               logger.info(
3536                   "Optimization did not yield a better energy. Reverting to original coordinates.");
3537               ResidueState.revertAllCoordinates(currentWindow, coordinates);
3538             } else {
3539               // Copy sliding window optimal rotamers into the overall optimum array.
3540               int i = 0;
3541               for (Residue residue : currentWindow) {
3542                 int index = residueList.indexOf(residue);
3543                 optimum[index] = optimumSubset[i++];
3544               }
3545             }
3546           } else {
3547             globalOptimization(currentWindow);
3548             // Copy sliding window optimal rotamers into the overall optimum array.
3549             int i = 0;
3550             for (Residue residue : currentWindow) {
3551               int index = residueList.indexOf(residue);
3552               optimum[index] = optimumSubset[i++];
3553             }
3554           }
3555 
3556           if (!incrementTruncated) {
3557             if (windowStart + (windowSize - 1) + increment > nOptimize - 1) {
3558               increment = nOptimize - windowStart - windowSize;
3559               if (increment == 0) {
3560                 break;
3561               }
3562               logger.warning(" Increment truncated in order to optimize entire residue range.");
3563               incrementTruncated = true;
3564             }
3565           }
3566 
3567           if (rank0 && printFiles) {
3568             File file = molecularAssembly.getFile();
3569             if (firstWindowSaved) {
3570               file.delete();
3571             }
3572             // Don't write a file if it's the final iteration.
3573             if (windowStart + windowSize == nOptimize) {
3574               continue;
3575             }
3576             PDBFilter windowFilter = new PDBFilter(file, molecularAssembly, null, null);
3577             //   StringBuilder header = new StringBuilder(format("Iteration %d of the sliding
3578             // window\n", counter - 1));
3579             try {
3580               windowFilter.writeFile(file, false);
3581               if (firstResidue != lastResidue) {
3582                 logger.info(
3583                     format(" File with residues %s ... %s in window written to.", firstResidue,
3584                         lastResidue));
3585               } else {
3586                 logger.info(format(" File with residue %s in window written to.", firstResidue));
3587               }
3588             } catch (Exception e) {
3589               logger.warning(format("Exception writing to file: %s", file.getName()));
3590             }
3591             firstWindowSaved = true;
3592           }
3593           long currentTime = System.nanoTime();
3594           windowTime += currentTime;
3595           logIfRank0(format(" Time elapsed for this iteration: %11.3f sec", windowTime * 1.0E-9));
3596           logIfRank0(
3597               format(" Overall time elapsed: %11.3f sec", (currentTime + beginTime) * 1.0E-9));
3598           if(genZ){
3599               int[] currentRotamers = new int[optimumSubset.length];
3600               usingBoxOptimization = true;
3601               Residue[] residueSubsetArray = currentWindow.toArray(new Residue[currentWindow.size()]);
3602               getFractions(residueSubsetArray,0,currentRotamers, true);
3603               Residue[] titrationResidueArray = new Residue[]{residueList.get(windowCenter)};
3604               getProtonationPopulations(titrationResidueArray);
3605           }
3606           if(windowCenter > -1){
3607               break;
3608           }
3609         }
3610 
3611 
3612         break;
3613       default:
3614         // No default case.
3615         break;
3616     }
3617     return 0.0;
3618   }
3619 
3620   /**
3621    * Sorts a passed List of Residues by global index.
3622    *
3623    * @param residues List of Residues to be sorted.
3624    */
3625   private void sortResidues(List<Residue> residues) {
3626     Comparator<Residue> comparator = Comparator.comparing(Residue::getChainID)
3627         .thenComparingInt(Residue::getResidueNumber);
3628     residues.sort(comparator);
3629   }
3630 
3631   /**
3632    * applyEliminationCriteria.
3633    *
3634    * @param residues    an array of {@link ffx.potential.bonded.Residue} objects.
3635    * @param getEnergies a boolean.
3636    * @param verbose     a boolean.
3637    */
3638   private void applyEliminationCriteria(Residue[] residues, boolean getEnergies, boolean verbose) {
3639     Level prevLevel = logger.getLevel();
3640     if (!verbose) {
3641       logger.setLevel(Level.WARNING);
3642     }
3643     if (getEnergies) {
3644       applyEliminationCriteria(residues);
3645     } else {
3646       int i = 0;
3647       boolean pairEliminated;
3648       do {
3649         pairEliminated = false;
3650         if (useGoldstein) {
3651           if (selfEliminationOn) {
3652             i++;
3653             logIfRank0(format("\n Iteration %d: Applying Single Goldstein DEE conditions ", i));
3654             // While there are eliminated rotamers, repeatedly apply single rotamer elimination.
3655             while (goldsteinDriver(residues)) {
3656               i++;
3657               logIfRank0(this.toString());
3658               logIfRank0(
3659                   format("\n Iteration %d: Applying Single Rotamer Goldstein DEE conditions ", i));
3660             }
3661           }
3662           if (pairEliminationOn) {
3663             i++;
3664             logIfRank0(
3665                 format("\n Iteration %d: Applying Rotamer Pair Goldstein DEE conditions ", i));
3666             // While there are eliminated rotamer pairs, repeatedly apply rotamer pair elimination.
3667             pairEliminated = false;
3668             while (goldsteinPairDriver(residues)) {
3669               pairEliminated = true;
3670               i++;
3671               logIfRank0(this.toString());
3672               logIfRank0(
3673                   format("\n Iteration %d: Applying Rotamer Pair Goldstein DEE conditions ", i));
3674             }
3675           }
3676         } else {
3677           if (selfEliminationOn) {
3678             i++;
3679             logIfRank0(format("\n Iteration %d: Applying Single DEE conditions ", i));
3680             // While there are eliminated rotamers, repeatedly apply single rotamer elimination.
3681             while (deeRotamerElimination(residues)) {
3682               i++;
3683               logIfRank0(toString());
3684               logIfRank0(format("\n Iteration %d: Applying Single Rotamer DEE conditions ", i));
3685             }
3686           }
3687           if (pairEliminationOn) {
3688             i++;
3689             logIfRank0(format("\n Iteration %d: Applying Rotamer Pair DEE conditions ", i));
3690             // While there are eliminated rotamer pairs, repeatedly apply rotamer pair elimination.
3691             pairEliminated = false;
3692             while (deeRotamerPairElimination(residues)) {
3693               pairEliminated = true;
3694               i++;
3695               logIfRank0(toString());
3696               logIfRank0(format("\n Iteration %d: Applying Rotamer Pair DEE conditions ", i));
3697             }
3698           }
3699         }
3700         eR.validateDEE(residues);
3701         logIfRank0(toString());
3702       } while (pairEliminated);
3703       logIfRank0(" Self-consistent DEE rotamer elimination achieved.\n");
3704     }
3705     if (!verbose) {
3706       logger.setLevel(prevLevel);
3707     }
3708   }
3709 
3710   private void applyEliminationCriteria(Residue[] residues) {
3711     if (verboseEnergies) {
3712       try {
3713         logIfRank0(format("\n Beginning Energy %s", formatEnergy(currentEnergy(residues))));
3714       } catch (ArithmeticException ex) {
3715         logger.severe(format(" Exception %s in calculating beginning energy; FFX shutting down.", ex));
3716       }
3717     }
3718 
3719     rotamerEnergies(residues);
3720 
3721     if (testing) {
3722       int nres = residues.length;
3723       eR.onlyPrunedSingles = new boolean[nres][];
3724       eR.onlyPrunedPairs = new boolean[nres][][][];
3725       for (int i = 0; i < nres; i++) {
3726         Residue residuei = residues[i];
3727         Rotamer[] rotamersi = residuei.getRotamers();
3728         int lenri = rotamersi.length; // Length rotamers i
3729         eR.onlyPrunedSingles[i] = new boolean[lenri];
3730         eR.onlyPrunedSingles[i] = copyOf(eR.eliminatedSingles[i], eR.eliminatedSingles[i].length);
3731         eR.onlyPrunedPairs[i] = new boolean[lenri][][];
3732         // Loop over the set of rotamers for residue i.
3733         for (int ri = 0; ri < lenri; ri++) {
3734           eR.onlyPrunedPairs[i][ri] = new boolean[nres][];
3735           for (int j = i + 1; j < nres; j++) {
3736             Residue residuej = residues[j];
3737             Rotamer[] rotamersj = residuej.getRotamers();
3738             int lenrj = rotamersj.length;
3739             eR.onlyPrunedPairs[i][ri][j] = new boolean[lenrj];
3740             eR.onlyPrunedPairs[i][ri][j] = copyOf(eR.eliminatedPairs[i][ri][j],
3741                 eR.eliminatedPairs[i][ri][j].length);
3742           }
3743         }
3744       }
3745     }
3746 
3747     if (testSelfEnergyEliminations) {
3748       testSelfEnergyElimination(residues);
3749     } else if (testPairEnergyEliminations > -1) {
3750       testPairEnergyElimination(residues, testPairEnergyEliminations);
3751     } else if (testTripleEnergyEliminations1 > -1 && testTripleEnergyEliminations2 > -1) {
3752       testTripleEnergyElimination(residues, testTripleEnergyEliminations1,
3753           testTripleEnergyEliminations2);
3754     }
3755 
3756     if (pruneClashes) {
3757       eR.validateDEE(residues);
3758     }
3759 
3760     int i = 0;
3761     boolean pairEliminated;
3762     do {
3763       pairEliminated = false;
3764       if (useGoldstein) {
3765         if (selfEliminationOn) {
3766           i++;
3767           logIfRank0(format("\n Iteration %d: Applying Single Goldstein DEE conditions ", i));
3768           // While there are eliminated rotamers, repeatedly apply single rotamer elimination.
3769           while (goldsteinDriver(residues)) {
3770             i++;
3771             logIfRank0(this.toString());
3772             logIfRank0(
3773                 format("\n Iteration %d: Applying Single Rotamer Goldstein DEE conditions ", i));
3774           }
3775         }
3776         if (pairEliminationOn) {
3777           i++;
3778           logIfRank0(format("\n Iteration %d: Applying Rotamer Pair Goldstein DEE conditions ", i));
3779           // While there are eliminated rotamer pairs, repeatedly apply rotamer pair elimination.
3780           while (goldsteinPairDriver(residues)) {
3781             pairEliminated = true;
3782             i++;
3783             logIfRank0(this.toString());
3784             logIfRank0(
3785                 format("\n Iteration %d: Applying Rotamer Pair Goldstein DEE conditions ", i));
3786           }
3787         }
3788       } else {
3789         if (selfEliminationOn) {
3790           i++;
3791           logIfRank0(format("\n Iteration %d: Applying Single DEE conditions ", i));
3792           // While there are eliminated rotamers, repeatedly apply single rotamer elimination.
3793           while (deeRotamerElimination(residues)) {
3794             i++;
3795             logIfRank0(toString());
3796             logIfRank0(format("\n Iteration %d: Applying Single Rotamer DEE conditions ", i));
3797           }
3798         }
3799         if (pairEliminationOn) {
3800           i++;
3801           logIfRank0(format("\n Iteration %d: Applying Rotamer Pair DEE conditions ", i));
3802           // While there are eliminated rotamer pairs, repeatedly apply rotamer pair elimination.
3803           while (deeRotamerPairElimination(residues)) {
3804             pairEliminated = true;
3805             i++;
3806             logIfRank0(toString());
3807             logIfRank0(format("\n Iteration %d: Applying Rotamer Pair DEE conditions ", i));
3808           }
3809         }
3810       }
3811       eR.validateDEE(residues);
3812       logIfRank0(toString());
3813     } while (pairEliminated);
3814 
3815     logIfRank0(" Self-consistent DEE rotamer elimination achieved.\n");
3816   }
3817 
3818   /**
3819    * Calculates the energy at the current state.
3820    *
3821    * @param resList List of residues in current energy term.
3822    * @return Energy of the current state.
3823    */
3824   private double currentEnergy(List<Residue> resList) throws ArithmeticException {
3825     List<Rotamer> rots = resList.stream().filter(Objects::nonNull).map(Residue::getRotamer)
3826         .collect(Collectors.toList());
3827     File energyDir = dirSupplier.apply(resList, rots);
3828     return eFunction.applyAsDouble(energyDir);
3829   }
3830 
3831   /**
3832    * Default method for obtaining energy: calculates energy of the Potential.
3833    *
3834    * @param dir Ignored, should be null
3835    * @return Current potential energy
3836    */
3837   private double currentPE(File dir) {
3838     if (x == null) {
3839       int nVar = potential.getNumberOfVariables();
3840       x = new double[nVar];
3841     }
3842     potential.getCoordinates(x);
3843     return potential.energy(x);
3844   }
3845 
3846   /**
3847    * Generates list of subsequent, neighboring Residues.
3848    *
3849    * @param residues To generate neighbor lists for.
3850    */
3851   private void generateResidueNeighbors(Residue[] residues) {
3852     int nRes = residues.length;
3853     resNeighbors = new int[nRes][];
3854     bidiResNeighbors = new int[nRes][];
3855     for (int i = 0; i < nRes; i++) {
3856       Set<Integer> nearby = new HashSet<>();
3857       Residue resi = residues[i];
3858       Rotamer[] rotsi = resi.getRotamers();
3859       int lenri = rotsi.length;
3860       int indexI = allResiduesList.indexOf(resi);
3861 
3862       for (int j = 0; j < nRes; j++) {
3863         if (i == j) {
3864           continue;
3865         }
3866         Residue resj = residues[j];
3867         Rotamer[] rotsj = resj.getRotamers();
3868         int lenrj = rotsj.length;
3869         int indexJ = allResiduesList.indexOf(resj);
3870 
3871         boolean foundClose = false;
3872         for (int ri = 0; ri < lenri; ri++) {
3873           for (int rj = 0; rj < lenrj; rj++) {
3874             if (!dM.checkPairDistThreshold(indexI, ri, indexJ, rj)) {
3875               foundClose = true;
3876               break;
3877             }
3878           }
3879           if (foundClose) {
3880             break;
3881           }
3882         }
3883         if (foundClose) {
3884           nearby.add(j);
3885         }
3886       }
3887 
3888       // Collect all neighbors.
3889       int[] nI = nearby.stream().mapToInt(Integer::intValue).toArray();
3890       bidiResNeighbors[i] = nI;
3891 
3892       // Collect only subsequent neighbors.
3893       final int fi = i; // Final copy of i.
3894       nI = nearby.stream().mapToInt(Integer::intValue).filter(j -> j > fi).toArray();
3895       resNeighbors[i] = nI;
3896     }
3897   }
3898 
3899   private void setUpRestart() {
3900     File restartFile;
3901     if (loadEnergyRestart) {
3902       restartFile = energyRestartFile;
3903     } else {
3904       File file = molecularAssembly.getFile();
3905       String filename = FilenameUtils.removeExtension(file.getAbsolutePath());
3906       Path restartPath = Paths.get(filename + ".restart");
3907       restartFile = restartPath.toFile();
3908       energyRestartFile = restartFile;
3909     }
3910     try {
3911       energyWriter = new BufferedWriter(new FileWriter(restartFile, true));
3912     } catch (IOException ex) {
3913       logger.log(Level.SEVERE, "Couldn't open energy restart file.", ex);
3914     }
3915     logger.info(format("\n Energy restart file: %s", restartFile.getName()));
3916   }
3917 
3918   /**
3919    * Turn off non-bonded contributions from all residues except for one. Compute the self-energy for
3920    * each residue relative to the backbone contribution.
3921    *
3922    * @param residues A list of residues that will undergo rotamer optimization.
3923    * @return template energy
3924    */
3925   private double rotamerEnergies(Residue[] residues) {
3926 
3927     if (residues == null) {
3928       logger.warning(" Attempt to compute rotamer energies for an empty array of residues.");
3929       return 0.0;
3930     }
3931 
3932     int nResidues = residues.length;
3933     Atom[] atoms = molecularAssembly.getAtomArray();
3934     generateResidueNeighbors(residues);
3935 
3936     eR = new EliminatedRotamers(this, dM, allResiduesList, maxRotCheckDepth, clashThreshold,
3937         pairClashThreshold, multiResClashThreshold, nucleicPruningFactor, nucleicPairsPruningFactor,
3938         multiResPairClashAddn, pruneClashes, prunePairClashes, print, residues);
3939 
3940     if (decomposeOriginal) {
3941       assert library.getUsingOrigCoordsRotamer();
3942       for (int i = 0; i < nResidues; i++) {
3943         Residue resi = residues[i];
3944         Rotamer[] rotsi = resi.getRotamers();
3945         int lenri = rotsi.length;
3946 
3947         // Leave the 0'th original-coordinates rotamer alone.
3948         for (int ri = 1; ri < lenri; ri++) {
3949           eR.eliminateRotamer(residues, i, ri, false);
3950         }
3951       }
3952     }
3953 
3954     // Initialize all atoms to be used.
3955     for (Atom atom : atoms) {
3956       atom.setUse(true);
3957     }
3958 
3959     eE = new EnergyExpansion(this, dM, eR, molecularAssembly, potential, algorithmListener,
3960         allResiduesList, resNeighbors, threeBodyTerm, decomposeOriginal, usingBoxOptimization,
3961         verbose, pruneClashes, prunePairClashes, rank0);
3962 
3963     // Update the EliminatedRotamers instance with the EnergyExpansion instance.
3964     eR.setEnergyExpansion(eE);
3965 
3966     int loaded = 0;
3967     if (loadEnergyRestart) {
3968       if (usingBoxOptimization) {
3969         loaded = eE.loadEnergyRestart(energyRestartFile, residues, boxOpt.boxLoadIndex, boxOpt.boxLoadCellIndices);
3970       } else {
3971         loaded = eE.loadEnergyRestart(energyRestartFile, residues);
3972       }
3973     }
3974 
3975     long energyStartTime = System.nanoTime();
3976     WorkerTeam energyWorkerTeam = new WorkerTeam(world);
3977 
3978     try {
3979       if (loaded < 1) {
3980         eE.allocateSelfJobMap(residues, nResidues, false);
3981       }
3982 
3983       SelfEnergyRegion selfEnergyRegion = new SelfEnergyRegion(this, eE, eR, residues, energyWriter,
3984           world, numProc, pruneClashes, rank0, rank, verbose, writeEnergyRestart, printFiles);
3985       energyWorkerTeam.execute(selfEnergyRegion);
3986       long singlesTime = System.nanoTime() - energyStartTime;
3987       logIfRank0(format(" Time for single energies: %12.4g", (singlesTime * 1.0E-9)));
3988       if (logger.isLoggable(Level.FINE)) {
3989         Resources.logResources();
3990       }
3991 
3992       if (loaded < 2) {
3993         eE.allocate2BodyJobMap(residues, nResidues, false);
3994       }
3995 
3996       TwoBodyEnergyRegion twoBodyEnergyRegion = new TwoBodyEnergyRegion(this, dM, eE, eR, residues,
3997           allResiduesList, energyWriter, world, numProc, prunePairClashes, superpositionThreshold,
3998           rank0, rank, verbose, writeEnergyRestart, printFiles);
3999       energyWorkerTeam.execute(twoBodyEnergyRegion);
4000       long pairsTime = System.nanoTime() - (singlesTime + energyStartTime);
4001 
4002       long triplesTime = 0;
4003       long quadsTime = 0;
4004       logIfRank0(format(" Time for 2-body energies:   %12.4g", (pairsTime * 1.0E-9)));
4005       if (logger.isLoggable(Level.FINE)) {
4006         Resources.logResources();
4007       }
4008 
4009       if (threeBodyTerm) {
4010         if (loaded < 3) {
4011           eE.allocate3BodyJobMap(residues, nResidues, false);
4012         }
4013         ThreeBodyEnergyRegion threeBodyEnergyRegion = new ThreeBodyEnergyRegion(this, dM, eE, eR,
4014             residues, allResiduesList, energyWriter, world, numProc, superpositionThreshold, rank0,
4015             rank, verbose, writeEnergyRestart, printFiles);
4016         energyWorkerTeam.execute(threeBodyEnergyRegion);
4017         triplesTime = System.nanoTime() - (pairsTime + singlesTime + energyStartTime);
4018         logIfRank0(format(" Time for 3-Body energies: %12.4g", (triplesTime * 1.0E-9)));
4019         if (logger.isLoggable(Level.FINE)) {
4020           Resources.logResources();
4021         }
4022       }
4023 
4024       if (compute4BodyEnergy) {
4025         eE.allocate4BodyJobMap(residues, nResidues);
4026 
4027         FourBodyEnergyRegion fourBodyEnergyRegion = new FourBodyEnergyRegion(this, dM, eE, eR,
4028             residues, allResiduesList, superpositionThreshold);
4029         energyWorkerTeam.execute(fourBodyEnergyRegion);
4030         quadsTime = System.nanoTime() - (triplesTime + pairsTime + singlesTime + energyStartTime);
4031         logIfRank0(format(" Time for 4-Body energies:   %12.4g", quadsTime * 1.0E-9));
4032       }
4033 
4034       long allTime = singlesTime + pairsTime + triplesTime + quadsTime;
4035       logIfRank0(format(" Time for all energies:    %12.4g", allTime * 1.0E-9));
4036     } catch (Exception ex) {
4037       String message = " Exception computing rotamer energies in parallel.";
4038       logger.log(Level.SEVERE, message, ex);
4039     }
4040 
4041     // Turn on all atoms.
4042     for (Atom atom : atoms) {
4043       atom.setUse(true);
4044     }
4045     // Print the energy with all rotamers in their default conformation.
4046     if (verboseEnergies && rank0) {
4047       try {
4048         double defaultEnergy = currentEnergy(residues);
4049         logger.info(format(" Energy of the system with rotamers in their default conformation: %s",
4050             formatEnergy(defaultEnergy)));
4051       } catch (ArithmeticException ex) {
4052         logger.severe(format(" Exception %s in calculating default energy; FFX shutting down", ex));
4053       }
4054     }
4055     return eE.getBackboneEnergy();
4056   }
4057 
4058   /**
4059    * Applies the "default" rotamer: currently the 0'th rotamer.
4060    *
4061    * @param residue Residue to apply a default rotamer for.
4062    */
4063   private void applyDefaultRotamer(Residue residue) {
4064     RotamerLibrary.applyRotamer(residue, residue.getRotamers()[0]);
4065   }
4066 
4067   /**
4068    * Elimination of rotamers via the original Dead End Elimination algorithm.
4069    *
4070    * @param residues Array of residues under consideration.
4071    * @return True if any rotamers were eliminated.
4072    */
4073   private boolean deeRotamerElimination(Residue[] residues) {
4074     int nres = residues.length;
4075     // A flag to indicate if more rotamers or rotamer pairs were eliminated.
4076     boolean eliminated = false;
4077     // Loop over residues.
4078     double[] minMax = new double[2];
4079     double[] minEnergySingles = null;
4080     double[] maxEnergySingles = null;
4081     for (int i = 0; i < nres; i++) {
4082       Residue residuei = residues[i];
4083       Rotamer[] rotamersi = residuei.getRotamers();
4084       int lenri = rotamersi.length;
4085       if (minEnergySingles == null || minEnergySingles.length < lenri) {
4086         minEnergySingles = new double[lenri];
4087         maxEnergySingles = new double[lenri];
4088       }
4089       // Loop over the set of rotamers for residue i.
4090       for (int ri = 0; ri < lenri; ri++) {
4091         // Check for an eliminated single.
4092         if (eR.check(i, ri)) {
4093           continue;
4094         }
4095         // Start the min/max summation with the self-energy.
4096         minEnergySingles[ri] = eE.getSelf(i, ri);
4097         maxEnergySingles[ri] = minEnergySingles[ri];
4098         for (int j = 0; j < nres; j++) {
4099           if (j == i) {
4100             continue;
4101           }
4102           if (eE.minMaxPairEnergy(residues, minMax, i, ri, j)) {
4103             if (Double.isFinite(minMax[0]) && Double.isFinite(minEnergySingles[ri])) {
4104               minEnergySingles[ri] += minMax[0];
4105             } else {
4106               // There is a chance that ri conflicts with every possible rotamer of some
4107               // residue j.
4108               // In that event, its minimum energy is set NaN and should be easy to eliminate.
4109               minEnergySingles[ri] = Double.NaN;
4110             }
4111             if (Double.isFinite(minMax[0]) && Double.isFinite(maxEnergySingles[ri])) {
4112               maxEnergySingles[ri] += minMax[1];
4113             } else {
4114               // In this branch, ri conflicts with some j,rj and cannot be practically used for
4115               // elimination.
4116               maxEnergySingles[ri] = Double.NaN;
4117             }
4118           } else {
4119             Residue residuej = residues[j];
4120             logger.info(
4121                 format(" Inconsistent Pair: %8s %2d, %8s.", residuei.toFormattedString(false, true),
4122                     ri, residuej.toFormattedString(false, true)));
4123             // eliminateRotamer(residues, i, ri, print);
4124           }
4125         }
4126       }
4127 
4128       /*
4129        Apply the singles elimination criteria to rotamers of residue i by determining the most
4130        favorable maximum energy.
4131       */
4132       double eliminationEnergy = Double.MAX_VALUE;
4133       int eliminatingRotamer = 0;
4134       for (int ri = 0; ri < lenri; ri++) {
4135         if (eR.check(i, ri)) {
4136           continue;
4137         }
4138         if (Double.isFinite(maxEnergySingles[ri]) && maxEnergySingles[ri] < eliminationEnergy) {
4139           eliminationEnergy = maxEnergySingles[ri];
4140           eliminatingRotamer = ri;
4141         }
4142       }
4143 
4144       if (eliminationEnergy == Double.MAX_VALUE) {
4145         // This branch is taken if every ri conflicts with at least one j,rj. In that case, nothing
4146         // can be eliminated yet!
4147         logIfRank0(" Could not eliminate any i,ri because eliminationEnergy was never set!",
4148             Level.FINE);
4149       } else {
4150         /*
4151          Eliminate rotamers whose minimum energy is greater than the worst case for another
4152          rotamer.
4153         */
4154         for (int ri = 0; ri < lenri; ri++) {
4155           if (eR.check(i, ri)) {
4156             continue;
4157           }
4158           // If i,ri has a clash with all phase space, it can be eliminated by something that
4159           // doesn't clash with all phase space.
4160           if (!Double.isFinite(minEnergySingles[ri])) {
4161             if (eR.eliminateRotamer(residues, i, ri, print)) {
4162               logIfRank0(format("  Rotamer elimination of (%8s,%2d) that always clashes.",
4163                   residuei.toFormattedString(false, true), ri));
4164               eliminated = true;
4165             }
4166           }
4167           // Otherwise, can eliminate if its best possible energy is still worse than something
4168           // else's worst possible energy.
4169           if (minEnergySingles[ri] > eliminationEnergy + ensembleBuffer) {
4170             if (eR.eliminateRotamer(residues, i, ri, print)) {
4171               logIfRank0(format("  Rotamer elimination of (%8s,%2d) by (%8s,%2d): %12.4f > %6.4f.",
4172                   residuei.toFormattedString(false, true), ri,
4173                   residuei.toFormattedString(false, true), eliminatingRotamer, minEnergySingles[ri],
4174                   eliminationEnergy + ensembleBuffer));
4175               eliminated = true;
4176             }
4177           }
4178         }
4179       }
4180     }
4181     return eliminated;
4182   }
4183 
4184   /**
4185    * Rotamer pair elimination driver for many-body Dead End Elimination. Generally less effective
4186    * than Goldstein.
4187    *
4188    * @param residues Residues under consideration.
4189    * @return If at least one pair eliminated.
4190    */
4191   private boolean deeRotamerPairElimination(Residue[] residues) {
4192     int nres = residues.length;
4193     boolean eliminated = false;
4194 
4195     for (int i = 0; i < (nres - 1); i++) {
4196       Residue residuei = residues[i];
4197       Rotamer[] rotamersi = residuei.getRotamers();
4198       int lenri = rotamersi.length;
4199 
4200       // Minimum and maximum summation found for ri-rj pairs.
4201       double[][] minPairEnergies = new double[lenri][];
4202       double[][] maxPairEnergies = new double[lenri][];
4203 
4204       for (int j = i + 1; j < nres; j++) {
4205         Residue residuej = residues[j];
4206         Rotamer[] rotamersj = residuej.getRotamers();
4207         int lenrj = rotamersj.length;
4208 
4209         for (int ri = 0; ri < lenri; ri++) {
4210           if (eR.check(i, ri)) {
4211             continue;
4212           }
4213           minPairEnergies[ri] = new double[lenrj];
4214           maxPairEnergies[ri] = new double[lenrj];
4215 
4216           for (int rj = 0; rj < lenrj; rj++) {
4217             if (eR.check(j, rj) || eR.check(i, ri, j, rj)) {
4218               continue;
4219             }
4220             minPairEnergies[ri][rj] =
4221                 eE.getSelf(i, ri) + eE.getSelf(j, rj) + eE.get2Body(i, ri, j, rj);
4222             maxPairEnergies[ri][rj] = minPairEnergies[ri][rj];
4223 
4224             // Min and max external summations for ri-rj.
4225             double[] minMax = new double[2];
4226 
4227             // Add contributions from third residues k, and possibly fourth residues l.
4228             if (eE.minMaxE2(residues, minMax, i, ri, j, rj)) {
4229               if (Double.isFinite(minPairEnergies[ri][rj]) && Double.isFinite(minMax[0])) {
4230                 minPairEnergies[ri][rj] += minMax[0];
4231               } else {
4232                 logger.severe(
4233                     format(" An ri-rj pair %s-%d %s-%d with NaN minimum was caught incorrectly!",
4234                         residuei.toFormattedString(false, true), ri,
4235                         residuej.toFormattedString(false, true), rj));
4236               }
4237               if (Double.isFinite(maxPairEnergies[ri][rj]) && Double.isFinite(minMax[1])) {
4238                 maxPairEnergies[ri][rj] += minMax[1];
4239               } else {
4240                 // ri-rj can clash, and isn't very useful to eliminate by.
4241                 maxPairEnergies[ri][rj] = Double.NaN;
4242               }
4243             } else {
4244               // A NaN minimum energy for some pair indicates it's definitely not part of the GMEC.
4245               minPairEnergies[ri][rj] = Double.NaN;
4246               logger.info(format(" Eliminating pair %s-%d %s-%d that always clashes.",
4247                   residuei.toFormattedString(false, true), ri,
4248                   residuej.toFormattedString(false, true), rj));
4249               eR.eliminateRotamerPair(residues, i, ri, j, rj, print);
4250               eliminated = true;
4251             }
4252           }
4253         }
4254 
4255         double pairEliminationEnergy = Double.MAX_VALUE;
4256         for (int ri = 0; ri < lenri; ri++) {
4257           if (eR.check(i, ri)) {
4258             continue;
4259           }
4260           for (int rj = 0; rj < lenrj; rj++) {
4261             if (eR.check(j, rj) || eR.check(i, ri, j, rj)) {
4262               continue;
4263             }
4264             if (Double.isFinite(maxPairEnergies[ri][rj])
4265                 && maxPairEnergies[ri][rj] < pairEliminationEnergy) {
4266               pairEliminationEnergy = maxPairEnergies[ri][rj];
4267             }
4268           }
4269         }
4270 
4271         if (pairEliminationEnergy == Double.MAX_VALUE) {
4272           logIfRank0(format(
4273               " All rotamer pairs for residues %s and %s have possible conflicts; cannot perform any eliminations!",
4274               residuei.toFormattedString(false, true), residuej), Level.FINE);
4275         } else {
4276           double comparisonEnergy = pairEliminationEnergy + ensembleBuffer;
4277           for (int ri = 0; ri < lenri; ri++) {
4278             if (eR.check(i, ri)) {
4279               continue;
4280             }
4281             for (int rj = 0; rj < lenrj; rj++) {
4282               if (eR.check(j, rj) || eR.check(i, ri, j, rj)) {
4283                 continue;
4284               }
4285               if (minPairEnergies[ri][rj] > comparisonEnergy) {
4286                 if (eR.eliminateRotamerPair(residues, i, ri, j, rj, print)) {
4287                   eliminated = true;
4288                   logIfRank0(format(" Eliminating rotamer pair: %s %d, %s %d (%s > %s + %6.6f)",
4289                       residuei.toFormattedString(false, true), ri,
4290                       residuej.toFormattedString(false, true), rj,
4291                       formatEnergy(minPairEnergies[ri][rj]), formatEnergy(pairEliminationEnergy),
4292                       ensembleBuffer), Level.INFO);
4293                 } else {
4294                   // See above check(i, ri, j, rj) for why this should not be taken!
4295                   logIfRank0(
4296                       format(" Already eliminated rotamer pair! %s %d, %s %d (%s > %1s + %6.6f)",
4297                           residuei.toFormattedString(false, true), ri,
4298                           residuej.toFormattedString(false, true), rj,
4299                           formatEnergy(minPairEnergies[ri][rj]), formatEnergy(pairEliminationEnergy),
4300                           ensembleBuffer), Level.WARNING);
4301                 }
4302               }
4303             }
4304           }
4305         }
4306 
4307         if (eR.pairsToSingleElimination(residues, i, j)) {
4308           eliminated = true;
4309         }
4310       }
4311     }
4312 
4313     return eliminated;
4314   }
4315 
4316   private boolean goldsteinDriver(Residue[] residues) {
4317     int nres = residues.length;
4318     // A flag to indicate if a rotamer is eliminated.
4319     boolean eliminated = false;
4320     // Loop over residue i.
4321     for (int i = 0; i < nres; i++) {
4322       Residue resi = residues[i];
4323       Rotamer[] roti = resi.getRotamers();
4324       int nri = roti.length;
4325       // Loop over the set of rotamers for residue i.
4326       for (int riA = 0; riA < nri; riA++) {
4327         // Continue if rotamer (i, riA) is not valid.
4328         if (eR.check(i, riA)) {
4329           continue;
4330         }
4331         for (int riB = 0; riB < nri; riB++) {
4332           // The eliminating rotamer cannot be riA and must be a valid.
4333           if (riA == riB || eR.check(i, riB)) {
4334             continue;
4335           }
4336           if (goldsteinElimination(residues, i, riA, riB)) {
4337             eliminated = true;
4338             break;
4339           }
4340         }
4341       }
4342     }
4343     if (!eliminated) {
4344       logIfRank0(" No more single rotamers to eliminate.");
4345     }
4346     return eliminated;
4347   }
4348 
4349   /**
4350    * Attemps to eliminate rotamer riA based on riB.
4351    *
4352    * @param residues Array of Residues.
4353    * @param i        Residue i index.
4354    * @param riA      Rotamer to attempt elimination of.
4355    * @param riB      Rotamer to attempt elimination by.
4356    * @return If riA was eliminated.
4357    */
4358   private boolean goldsteinElimination(Residue[] residues, int i, int riA, int riB) {
4359     Residue resi = residues[i];
4360 
4361     // Initialize Goldstein inequality.
4362     double selfDiff = eE.getSelf(i, riA) - eE.getSelf(i, riB);
4363     double goldsteinEnergy = selfDiff;
4364 
4365     double sumPairDiff = 0.0;
4366     double sumTripleDiff = 0.0;
4367 
4368     // Loop over a 2nd residue j.
4369     for (int j : bidiResNeighbors[i]) {
4370       Residue resj = residues[j];
4371       Rotamer[] rotj = resj.getRotamers();
4372       int nrj = rotj.length;
4373       double minForResJ = Double.MAX_VALUE;
4374       double minPairDiff = 0.0;
4375       double minTripleDiff = 0.0;
4376       int rjEvals = 0;
4377 
4378       // Loop over the rotamers for residue j.
4379       for (int rj = 0; rj < nrj; rj++) {
4380         if (eR.check(j, rj)) {
4381           continue;
4382         }
4383 
4384         if (eR.check(i, riA, j, rj)) {
4385           continue; // This is not a part of configuration space accessible to riA.
4386         }
4387         if (eR.check(i, riB, j, rj)) {
4388           /*
4389            This is a part of configuration space where riA is valid but not riB. Thus, if j,rj is
4390            part of the GMEC, riB is inconsistent with it. Thus, riB cannot be used to eliminate
4391            riA.
4392           */
4393           return false;
4394         }
4395 
4396         double pairI = eE.get2Body(i, riA, j, rj);
4397         double pairJ = eE.get2Body(i, riB, j, rj);
4398         double pairDiff = pairI - pairJ;
4399 
4400         rjEvals++;
4401 
4402         // Include three-body interactions.
4403         double tripleDiff = 0.0;
4404         if (threeBodyTerm) {
4405           IntStream kStream = IntStream.concat(Arrays.stream(bidiResNeighbors[i]),
4406               Arrays.stream(bidiResNeighbors[j]));
4407           int[] possKs = kStream.distinct().sorted().toArray();
4408           for (int k : possKs) {
4409             if (k == i || k == j) {
4410               continue;
4411             }
4412             Residue resk = residues[k];
4413             Rotamer[] rotk = resk.getRotamers();
4414             int nrk = rotk.length;
4415             int rkEvals = 0;
4416             double minForResK = Double.MAX_VALUE;
4417             for (int rk = 0; rk < nrk; rk++) {
4418               /*
4419                If k,rk or j,rj-k,rk are not a part of valid configuration space, continue. If
4420                i,riA-k,rk or i,riA-j,rj-k,rk are not valid for riA, continue.
4421               */
4422               if (eR.check(k, rk) || eR.check(j, rj, k, rk) || eR.check(i, riA, k, rk)) {
4423                 // Not yet implemented: check(i, riA, j, rj, k, rk) because no triples get
4424                 // eliminated.
4425                 continue;
4426               }
4427               /*
4428                If i,riB-k,rk or i,riB-j,rj-k,rk are invalid for riB, there is some part of
4429                configuration space for which riA is valid but not riB.
4430               */
4431               if (eR.check(i, riB, k, rk)) {
4432                 // Not yet implemented: check(i, riB, j, rj, k, rk).
4433                 return false;
4434               }
4435 
4436               rkEvals++;
4437               double e =
4438                   eE.get3Body(residues, i, riA, j, rj, k, rk) - eE.get3Body(residues, i, riB, j, rj,
4439                       k, rk);
4440               if (e < minForResK) {
4441                 minForResK = e;
4442               }
4443             }
4444             /* If there were no 3-body interactions with residue k, then minForResk is zero. */
4445             if (rkEvals == 0) {
4446               minForResK = 0.0;
4447             }
4448             tripleDiff += minForResK;
4449           }
4450         }
4451 
4452         double sumOverK = pairDiff + tripleDiff;
4453         if (sumOverK < minForResJ) {
4454           minForResJ = sumOverK;
4455           minPairDiff = pairDiff;
4456           minTripleDiff = tripleDiff;
4457         }
4458       }
4459       // If there are no 2-body interactions, then minForResJ is zero.
4460       if (rjEvals == 0) {
4461         minForResJ = 0.0;
4462         minPairDiff = 0.0;
4463         minTripleDiff = 0.0;
4464       }
4465       sumPairDiff += minPairDiff;
4466       sumTripleDiff += minTripleDiff;
4467       goldsteinEnergy += minForResJ;
4468     }
4469 
4470     if (goldsteinEnergy > ensembleBuffer) {
4471       if (eR.eliminateRotamer(residues, i, riA, print)) {
4472         logIfRank0(format("  Rotamer elimination of (%8s,%2d) by (%8s,%2d): %12.4f > %6.4f.",
4473             resi.toFormattedString(false, true), riA, resi.toFormattedString(false, true), riB,
4474             goldsteinEnergy, ensembleBuffer));
4475         logIfRank0(format("   Self: %12.4f, Pairs: %12.4f, Triples: %12.4f.", selfDiff, sumPairDiff,
4476             sumTripleDiff));
4477         return true;
4478       }
4479     }
4480     return false;
4481   }
4482 
4483   /**
4484    * Finds and eliminates rotamer pairs according to the many-body Goldstein pairs criterion.
4485    *
4486    * @param residues Residues under consideration.
4487    * @return If any rotamer pairs were eliminated.
4488    */
4489   private boolean goldsteinPairDriver(Residue[] residues) {
4490     int nRes = residues.length;
4491     boolean eliminated = false;
4492 
4493     // First, generate pairs riA-rjC.
4494     for (int i = 0; i < nRes; i++) {
4495       Residue resi = residues[i];
4496       Rotamer[] rotsi = resi.getRotamers();
4497       int lenri = rotsi.length;
4498 
4499       for (int riA = 0; riA < lenri; riA++) {
4500         // Don't try to eliminate that which is already eliminated.
4501         if (eR.check(i, riA)) {
4502           continue;
4503         }
4504 
4505         // Residue j can be any other residue, including ones before residue i.
4506         for (int j = 0; j < nRes; j++) {
4507           // Residue j must be distinct from i.
4508           if (i == j) {
4509             continue;
4510           }
4511           Residue resj = residues[j];
4512           Rotamer[] rotsj = resj.getRotamers();
4513           int lenrj = rotsj.length;
4514           for (int rjC = 0; rjC < lenrj; rjC++) {
4515             // Again, no point in eliminating the already-eliminated.
4516             if (eR.check(j, rjC) || eR.check(i, riA, j, rjC)) {
4517               continue;
4518             }
4519             boolean breakOut = false;
4520 
4521             // Now, generate pairs riB-rjD. If any pair riB-rjD eliminates riA-rjC, break out of the
4522             // loop.
4523             for (int riB = 0; riB < lenri; riB++) {
4524               if (breakOut) {
4525                 break;
4526               }
4527               if (eR.check(i, riB)) {
4528                 continue;
4529               }
4530               for (int rjD = 0; rjD < lenrj; rjD++) {
4531                 if (breakOut) {
4532                   break;
4533                 }
4534                 // Do not attempt eliminating with an eliminated pair.
4535                 if (eR.check(j, rjD) || eR.check(i, riB, j, rjD)) {
4536                   continue;
4537                 }
4538                 // Do not attempt to eliminate a pair with itself.
4539                 if (riA == riB && rjC == rjD) {
4540                   continue;
4541                 }
4542                 if (goldsteinPairElimination(residues, i, riA, riB, j, rjC, rjD)) {
4543                   breakOut = true;
4544                   eliminated = true;
4545                 }
4546               }
4547             }
4548           }
4549           if (eR.pairsToSingleElimination(residues, i, j)) {
4550             eliminated = true;
4551           }
4552         }
4553       }
4554     }
4555     return eliminated;
4556   }
4557 
4558   /**
4559    * Attempt to eliminate rotamer pair (ResI-RotA, ResJ-RotC) using (ResI-RotB, ResJ-RotD).
4560    *
4561    * @param residues Array of residues.
4562    * @param i        Index of the first residue.
4563    * @param riA      Index of the first residue's rotamer to eliminate.
4564    * @param riB      Index of the first residue's rotamer to use for elimination.
4565    * @param j        Index of the 2nd residue.
4566    * @param rjC      Index of the 2nd residue's rotamer to eliminate.
4567    * @param rjD      Index of the 2nd residue's rotamer to use for elimination.
4568    * @return Return true if eliminated.
4569    */
4570   private boolean goldsteinPairElimination(Residue[] residues, int i, int riA, int riB, int j,
4571                                            int rjC, int rjD) {
4572 
4573     List<Residue> missedResidues = null;
4574 
4575     // Initialize the Goldstein energy.
4576     double goldsteinEnergy =
4577         eE.getSelf(i, riA) + eE.getSelf(j, rjC) + eE.get2Body(i, riA, j, rjC) - eE.getSelf(i, riB)
4578             - eE.getSelf(j, rjD) - eE.get2Body(i, riB, j, rjD);
4579 
4580     try {
4581       if (parallelTeam == null) {
4582         parallelTeam = new ParallelTeam();
4583       }
4584       if (goldsteinPairRegion == null) {
4585         goldsteinPairRegion = new GoldsteinPairRegion(parallelTeam.getThreadCount());
4586       }
4587       goldsteinPairRegion.init(residues, i, riA, riB, j, rjC, rjD, bidiResNeighbors, this);
4588       parallelTeam.execute(goldsteinPairRegion);
4589       goldsteinEnergy += goldsteinPairRegion.getSumOverK();
4590       missedResidues = goldsteinPairRegion.getMissedResidues();
4591     } catch (Exception e) {
4592       logger.log(Level.WARNING, " Exception in GoldsteinPairRegion.", e);
4593     }
4594     if (missedResidues != null && !missedResidues.isEmpty()) {
4595       logIfRank0(format(
4596           " Skipping energy comparison due to a missed residue: i %d riA %d riB %d j %d rjC %d rjD %d",
4597           i, riA, riB, j, rjC, rjD), Level.FINE);
4598       return false;
4599     }
4600 
4601     if (goldsteinEnergy > ensembleBuffer) {
4602       if (missedResidues.isEmpty()) {
4603         if (eR.eliminateRotamerPair(residues, i, riA, j, rjC, print)) {
4604           logIfRank0(format(
4605               "  Pair elimination of [(%8s,%2d),(%8s,%2d)] by [(%8s,%2d),(%8s,%2d)]: %12.4f > %6.4f",
4606               residues[i].toFormattedString(false, true), riA,
4607               residues[j].toFormattedString(false, true), rjC,
4608               residues[i].toFormattedString(false, true), riB,
4609               residues[j].toFormattedString(false, true), rjD, goldsteinEnergy, ensembleBuffer));
4610           return true;
4611         }
4612       } else {
4613         logIfRank0(format(
4614             "  No Pair elimination of [(%8s,%2d),(%8s,%2d)] by [(%8s,%2d),(%8s,%2d)]: %12.4f > %6.4f",
4615             residues[i].toFormattedString(false, true), riA,
4616             residues[j].toFormattedString(false, true), rjC,
4617             residues[i].toFormattedString(false, true), riB,
4618             residues[j].toFormattedString(false, true), rjD, goldsteinEnergy, ensembleBuffer));
4619         StringBuilder sb = new StringBuilder();
4620         for (Residue residueM : missedResidues) {
4621           sb.append(residueM);
4622         }
4623         logIfRank0(format("   due to %s.", sb));
4624       }
4625     }
4626     return false;
4627   }
4628 
4629   /**
4630    * Method allows for testing of the elimination criteria by setting parameters appropriately.
4631    *
4632    * @param testing True only during RotamerOptimizationTest.
4633    */
4634   void setTestOverallOpt(boolean testing) {
4635     this.testing = testing;
4636     distanceMethod = DistanceMethod.ROTAMER;
4637     setTwoBodyCutoff(Double.MAX_VALUE);
4638     setThreeBodyCutoff(9.0);
4639   }
4640 
4641   /**
4642    * Method allows for testing of the elimination criteria by setting parameters appropriately for a
4643    * specific test case.
4644    *
4645    * @param testSelfEnergyEliminations True when only self energies are calculated; pairs,
4646    *                                   triples, etc., are assumed to be 0.
4647    */
4648   void setTestSelfEnergyEliminations(boolean testSelfEnergyEliminations) {
4649     this.testing = true;
4650     this.testSelfEnergyEliminations = testSelfEnergyEliminations;
4651     testPairEnergyEliminations = -1;
4652     testTripleEnergyEliminations1 = -1;
4653     testTripleEnergyEliminations2 = -1;
4654     distanceMethod = DistanceMethod.ROTAMER;
4655     setTwoBodyCutoff(Double.MAX_VALUE);
4656     setThreeBodyCutoff(9.0);
4657   }
4658 
4659   /**
4660    * Method allows for testing of the elimination criteria by setting parameters appropriately for a
4661    * specific test case.
4662    *
4663    * @param testPairEnergyEliminations True when only pair energies are calculated; selves,
4664    *                                   triples, etc., are assumed to be 0.
4665    */
4666   void setTestPairEnergyEliminations(int testPairEnergyEliminations) {
4667     this.testing = true;
4668     this.testPairEnergyEliminations = testPairEnergyEliminations;
4669     testSelfEnergyEliminations = false;
4670     testTripleEnergyEliminations1 = -1;
4671     testTripleEnergyEliminations2 = -1;
4672     distanceMethod = DistanceMethod.ROTAMER;
4673     setTwoBodyCutoff(Double.MAX_VALUE);
4674     setThreeBodyCutoff(9.0);
4675   }
4676 
4677   /**
4678    * Method allows for testing of the elimination criteria by setting parameters appropriately for a
4679    * specific test case.
4680    *
4681    * @param testTripleEnergyEliminations1 True when only triple energies are calculated; selves,
4682    *                                      pairs, etc., are assumed to be 0.
4683    * @param testTripleEnergyEliminations2 True when only triple energies are calculated; selves,
4684    *                                      pairs, etc., are assumed to be 0.
4685    */
4686   void setTestTripleEnergyEliminations(int testTripleEnergyEliminations1,
4687                                        int testTripleEnergyEliminations2) {
4688     this.testing = true;
4689     this.testTripleEnergyEliminations1 = testTripleEnergyEliminations1;
4690     this.testTripleEnergyEliminations2 = testTripleEnergyEliminations2;
4691     testSelfEnergyEliminations = false;
4692     testPairEnergyEliminations = -1;
4693     distanceMethod = DistanceMethod.ROTAMER;
4694     setTwoBodyCutoff(Double.MAX_VALUE);
4695     setThreeBodyCutoff(9.0);
4696   }
4697 
4698   /**
4699    * Test the self-energy elimination by setting 2-body and 3-body interactions to zero.
4700    *
4701    * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
4702    */
4703   private void testSelfEnergyElimination(Residue[] residues) {
4704     int nRes = residues.length;
4705     for (int i = 0; i < nRes; i++) {
4706       Residue resI = residues[i];
4707       Rotamer[] rotI = resI.getRotamers();
4708       int nI = rotI.length;
4709       for (int ri = 0; ri < nI; ri++) {
4710         for (int j = i + 1; j < nRes; j++) {
4711           Residue resJ = residues[j];
4712           Rotamer[] rotJ = resJ.getRotamers();
4713           int nJ = rotJ.length;
4714           for (int rj = 0; rj < nJ; rj++) {
4715             try {
4716               eE.set2Body(i, ri, j, rj, 0, true);
4717             } catch (Exception e) {
4718               // catch NPE.
4719             }
4720             if (threeBodyTerm) {
4721               for (int k = j + 1; k < nRes; k++) {
4722                 Residue resK = residues[k];
4723                 Rotamer[] rotK = resK.getRotamers();
4724                 int nK = rotK.length;
4725                 for (int rk = 0; rk < nK; rk++) {
4726                   try {
4727                     eE.set3Body(residues, i, ri, j, rj, k, rk, 0, true);
4728                   } catch (Exception e) {
4729                     // catch NPE.
4730                   }
4731                 }
4732               }
4733             }
4734           }
4735         }
4736       }
4737     }
4738   }
4739 
4740   /**
4741    * Test the elimination criteria by setting self and 3-body interactions to zero.
4742    *
4743    * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
4744    * @param resID    The residue to test.
4745    */
4746   private void testPairEnergyElimination(Residue[] residues, int resID) {
4747     int nRes = residues.length;
4748 
4749     if (resID >= nRes) {
4750       return;
4751     }
4752 
4753     for (int i = 0; i < nRes; i++) {
4754       Residue resI = residues[i];
4755       Rotamer[] rotI = resI.getRotamers();
4756       int nI = rotI.length;
4757       for (int ri = 0; ri < nI; ri++) {
4758         try {
4759           eE.setSelf(i, ri, 0, true);
4760         } catch (Exception e) {
4761           // catch NPE.
4762         }
4763         for (int j = i + 1; j < nRes; j++) {
4764           Residue resJ = residues[j];
4765           Rotamer[] rotJ = resJ.getRotamers();
4766           int nJ = rotJ.length;
4767           for (int rj = 0; rj < nJ; rj++) {
4768             if (i != resID && j != resID) {
4769               try {
4770                 eE.set2Body(i, ri, j, rj, 0, true);
4771               } catch (Exception e) {
4772                 // catch NPE.
4773               }
4774             }
4775             if (threeBodyTerm) {
4776               for (int k = j + 1; k < nRes; k++) {
4777                 Residue resK = residues[k];
4778                 Rotamer[] rotK = resK.getRotamers();
4779                 int nK = rotK.length;
4780                 for (int rk = 0; rk < nK; rk++) {
4781                   try {
4782                     eE.set3Body(residues, i, ri, j, rj, k, rk, 0, true);
4783                   } catch (Exception e) {
4784                     // catch NPE.
4785                   }
4786                 }
4787               }
4788             }
4789           }
4790         }
4791       }
4792     }
4793   }
4794 
4795   /**
4796    * Test the elimination criteria by setting self and 2-body interactions to zero. Two residues are
4797    * at fixed rotamers and all rotamer interactions with those two residues are calculated.
4798    *
4799    * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
4800    * @param resID1   The residue number for one of two fixed residues.
4801    * @param resID2   The second residue number for one of two fixed residues.
4802    */
4803   private void testTripleEnergyElimination(Residue[] residues, int resID1, int resID2) {
4804     int nRes = residues.length;
4805 
4806     if (resID1 >= nRes) {
4807       return;
4808     }
4809     if (resID2 >= nRes) {
4810       return;
4811     }
4812     if (resID1 == resID2) {
4813       return;
4814     }
4815 
4816     for (int i = 0; i < nRes; i++) {
4817       Residue resI = residues[i];
4818       Rotamer[] rotI = resI.getRotamers();
4819       int nI = rotI.length;
4820       for (int ri = 0; ri < nI; ri++) {
4821         try {
4822           eE.setSelf(i, ri, 0, true);
4823         } catch (Exception e) {
4824           // catch NPE.
4825         }
4826         for (int j = i + 1; j < nRes; j++) {
4827           Residue resJ = residues[j];
4828           Rotamer[] rotJ = resJ.getRotamers();
4829           int nJ = rotJ.length;
4830           for (int rj = 0; rj < nJ; rj++) {
4831             /*
4832              if (i != resID1 && j != resID1) { try { twoBodyEnergy[i][ri][j][rj] = 0.0; } catch
4833              (Exception e) { // catch NPE. } }
4834             */
4835             try {
4836               eE.set2Body(i, ri, j, rj, 0, true);
4837             } catch (Exception e) {
4838               // catch NPE.
4839             }
4840             if (threeBodyTerm) {
4841               for (int k = j + 1; k < nRes; k++) {
4842                 Residue resK = residues[k];
4843                 Rotamer[] rotK = resK.getRotamers();
4844                 int nK = rotK.length;
4845                 for (int rk = 0; rk < nK; rk++) {
4846 
4847                   if (i != resID1 && j != resID1 && k != resID1) {
4848                     try {
4849                       eE.set3Body(residues, i, ri, j, rj, k, rk, 0, true);
4850                     } catch (Exception e) {
4851                       // catch NPE.
4852                     }
4853                   }
4854                   if (i != resID2 && j != resID2 && k != resID2) {
4855                     try {
4856                       eE.set3Body(residues, i, ri, j, rj, k, rk, 0, true);
4857                     } catch (Exception e) {
4858                       // catch NPE.
4859                     }
4860                   }
4861                 }
4862               }
4863             }
4864           }
4865         }
4866       }
4867     }
4868   }
4869 
4870   /**
4871    * Rotamer Optimization Methods.
4872    */
4873   public enum Algorithm {
4874     INDEPENDENT,  // 1
4875     ALL,          // 2
4876     BRUTE_FORCE,  // 3
4877     WINDOW,       // 4
4878     BOX;          // 5
4879 
4880     public static Algorithm getAlgorithm(int algorithm) {
4881       switch (algorithm) {
4882         case 1:
4883           return INDEPENDENT;
4884         case 2:
4885           return ALL;
4886         case 3:
4887           return BRUTE_FORCE;
4888         case 4:
4889           return WINDOW;
4890         case 5:
4891           return BOX;
4892         default:
4893           throw new IllegalArgumentException(
4894               format(" Algorithm choice was %d, not in range 1-5!", algorithm));
4895       }
4896     }
4897   }
4898 
4899   /**
4900    * Allows get2BodyDistance to find the shortest distance between any two rotamers or two residues.
4901    */
4902   public enum DistanceMethod {
4903     ROTAMER, RESIDUE
4904   }
4905 
4906   public enum Direction {
4907     FORWARD, BACKWARD
4908   }
4909 
4910     //    /**
4911   //     * Writes eliminated singles and pairs to a CSV file. Reads in .log file.
4912   //     *
4913   //     * @throws java.io.FileNotFoundException if any.
4914   //     * @throws java.io.IOException if any.
4915   //     */
4916   //    public void outputEliminated() throws FileNotFoundException, IOException {
4917   //        /**
4918   //         * eliminated.csv stores the eliminated singles and pairs.
4919   //         */
4920   //        File fileName = new File("eliminated.csv");
4921   //        String path = fileName.getCanonicalPath();
4922   //        File outputTxt = new File(path);
4923   //        Path currentRelativePath = Paths.get("eliminated.csv");
4924   //        String logPath = currentRelativePath.toAbsolutePath().toString();
4925   //        logPath = logPath.replaceAll("/eliminated.csv", "");
4926   //        File logDirectory = new File(logPath);
4927   //        /**
4928   //         * Searches for .log file within the directory.
4929   //         */
4930   //        File[] logArray = logDirectory.listFiles(new FilenameFilter() {
4931   //            public boolean accept(File dir, String filename) {
4932   //                return filename.endsWith(".log");
4933   //            }
4934   //        });
4935   //        File logFile = logArray[0];
4936   //        PrintWriter out = new PrintWriter(new FileWriter(outputTxt, true));
4937   //        BufferedReader br = new BufferedReader(new FileReader(logFile));
4938   //        String line;
4939   //        String line3;
4940   //        List<String> boxes = new ArrayList<String>();
4941   //        while ((line3 = br.readLine()) != null) {
4942   //            if (line3.contains("-a, 5")) {
4943   //                /**
4944   //                 * Stores the number of box optimization iterations and the
4945   //                 * coordinates of each box.
4946   //                 */
4947   //                while ((line = br.readLine()) != null) {
4948   //                    if (line.contains("xyz indices")) {
4949   //                        String coordinates = line.replaceAll(" Cell xyz indices:", "");
4950   //                        String nextLine = br.readLine();
4951   //                        if (!nextLine.contains("Empty box")) {
4952   //                            boxes.add(coordinates);
4953   //                            String nextLine2;
4954   //                            String nextLine4;
4955   //                            String nextLine5;
4956   //                            while (!(nextLine2 = br.readLine()).contains("Collecting
4957   // Permutations")) {
4958   //                                if (nextLine2.contains("Applying Rotamer Pair")) {
4959   //                                    int total = 0;
4960   //                                    int total2 = 0;
4961   //                                    nextLine2 = br.readLine();
4962   //                                    nextLine4 = br.readLine();
4963   //                                    nextLine5 = br.readLine();
4964   //                                    if (nextLine5.contains("Self-consistent")) {
4965   //                                        String[] split = nextLine2.split(" ");
4966   //                                        int singlesAmount = Integer.parseInt(split[1]);
4967   //                                        if (singlesIterations.size() > 0) {
4968   //                                            total =
4969   // singlesIterations.get(singlesIterations.size() - 1);
4970   //                                        }
4971   //                                        singlesIterations.add(singlesAmount + total);
4972   //                                        System.out.println(nextLine4);
4973   //                                        String[] split2 = nextLine4.split(" ");
4974   //                                        int pairsAmount = Integer.parseInt(split2[1]);
4975   //                                        if (pairsIterations.size() > 0) {
4976   //                                            total2 = pairsIterations.get(pairsIterations.size()
4977   // - 1);
4978   //                                        }
4979   //                                        pairsIterations.add(pairsAmount + total2);
4980   //                                    }
4981   //                                }
4982   //                            }
4983   //                        }
4984   //                    }
4985   //                }
4986   //                /**
4987   //                 * Writes eliminated singles and pairs to eliminated.csv, sorted
4988   //                 * by box iteration.
4989   //                 */
4990   //                out.append("Eliminated Singles\n");
4991   //                out.append("Residue,Rotamer\n");
4992   //                out.append(boxes.get(0) + "\n");
4993   //                int stopper = 0;
4994   //                for (int i = 0; i < eliminatedResidue.size(); i++) {
4995   //                    while (stopper == 0) {
4996   //                        for (int x = 0; x < (singlesIterations.size() - 1); x++) {
4997   //                            if (singlesIterations.get(x) == 0) {
4998   //                                String boxHeader = (boxes.get(x + 1));
4999   //                                out.append(boxHeader + System.lineSeparator());
5000   //                            }
5001   //                            stopper++;
5002   //                        }
5003   //                    }
5004   //                    String first = eliminatedResidue.get(i).toString();
5005   //                    String second = eliminatedRotamer.get(i).toString();
5006   //                    String resrot = first + "," + second;
5007   //                    out.append(resrot + System.lineSeparator());
5008   //                    for (int x = 0; x < (singlesIterations.size() - 1); x++) {
5009   //                        if ((i + 1) == singlesIterations.get(x)) {
5010   //                            String boxHeader = (boxes.get(x + 1));
5011   //                            out.append(boxHeader + System.lineSeparator());
5012   //                        }
5013   //                    }
5014   //                }
5015   //                out.append("Eliminated Pairs\n");
5016   //                out.append("Residue1,Rotamer1,Residue2,Rotamer2" + System.lineSeparator());
5017   //                out.append(boxes.get(0) + "\n");
5018   //                int stopper2 = 0;
5019   //                for (int i = 0; i < eliminatedResidue1.size(); i++) {
5020   //                    while (stopper2 == 0) {
5021   //                        for (int x = 0; x < (pairsIterations.size() - 1); x++) {
5022   //                            if (pairsIterations.get(x) == 0) {
5023   //                                String boxHeader = (boxes.get(x + 1));
5024   //                                out.append(boxHeader + System.lineSeparator());
5025   //                            }
5026   //                            stopper2++;
5027   //                        }
5028   //                    }
5029   //                    String first = eliminatedResidue1.get(i).toString();
5030   //                    String second = eliminatedRotamer1.get(i).toString();
5031   //                    String third = eliminatedResidue2.get(i).toString();
5032   //                    String fourth = eliminatedRotamer2.get(i).toString();
5033   //                    String resrot = first + "," + second + "," + third + "," + fourth;
5034   //                    out.append(resrot + System.lineSeparator());
5035   //                    for (int x = 0; x < (pairsIterations.size() - 1); x++) {
5036   //                        if ((i + 1) == pairsIterations.get(x)) {
5037   //                            String boxHeader = (boxes.get(x + 1));
5038   //                            out.append(boxHeader + System.lineSeparator());
5039   //                        }
5040   //                    }
5041   //                }
5042   //                /**
5043   //                 * Writes eliminated singles and pairs to eliminated.csv for
5044   //                 * global optimization.
5045   //                 */
5046   //            } else if ((line3.contains("-a, 1")) || (line3.contains("-a, 2")) ||
5047   // (line3.contains("-a, 3")) || (line3.contains("-a, 4"))) {
5048   //                out.append("Eliminated Singles\n");
5049   //                out.append("Residue,Rotamer\n");
5050   //                for (int i = 0; i < eliminatedResidue.size(); i++) {
5051   //                    String first = eliminatedResidue.get(i).toString();
5052   //                    String second = eliminatedRotamer.get(i).toString();
5053   //                    String resrot = first + "," + second;
5054   //                    out.append(resrot + System.lineSeparator());
5055   //                }
5056   //                out.append("Eliminated Pairs\n");
5057   //                out.append("Residue1,Rotamer1,Residue2,Rotamer2" + System.lineSeparator());
5058   //                for (int i = 0; i < eliminatedResidue1.size(); i++) {
5059   //                    String first = eliminatedResidue1.get(i).toString();
5060   //                    String second = eliminatedRotamer1.get(i).toString();
5061   //                    String third = eliminatedResidue2.get(i).toString();
5062   //                    String fourth = eliminatedRotamer2.get(i).toString();
5063   //                    String resrot = first + "," + second + "," + third + "," + fourth;
5064   //                    out.append(resrot + System.lineSeparator());
5065   //                }
5066   //            }
5067   //        }
5068   //        out.close();
5069   //        br.close();
5070   //    }
5071 }