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