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