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