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-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.algorithms.optimize.manybody;
39  
40  import ffx.algorithms.optimize.RotamerOptimization;
41  import ffx.potential.bonded.MultiResidue;
42  import ffx.potential.bonded.Residue;
43  import ffx.potential.bonded.Rotamer;
44  
45  import java.util.List;
46  import java.util.logging.Level;
47  import java.util.logging.Logger;
48  import java.util.stream.IntStream;
49  
50  import static ffx.potential.bonded.Residue.ResidueType.NA;
51  import static java.lang.String.format;
52  
53  public class EliminatedRotamers {
54  
55    private static final Logger logger = Logger.getLogger(EliminatedRotamers.class.getName());
56  
57    private final RotamerOptimization rO;
58    private final DistanceMatrix dM;
59    /**
60     * A list of all residues being optimized. Note that Box and Window optimizations operate on
61     * subsets of this list.
62     */
63    private final List<Residue> allResiduesList;
64    /**
65     * Maximum depth to check if a rotamer can be eliminated.
66     */
67    private final int maxRotCheckDepth;
68    /**
69     * Clash energy threshold (kcal/mole).
70     */
71    private final double clashThreshold;
72    /**
73     * Clash energy threshold (kcal/mole).
74     */
75    private final double pairClashThreshold;
76    /**
77     * Clash energy threshold (kcal/mol) for MultiResidues, which can have much more variation in self
78     * and 2-Body energies.
79     */
80    private final double multiResClashThreshold;
81    /**
82     * Factor by which to multiply the pruning constraints for nucleic acids. nucleicPairsPruningFactor
83     * is the arithmetic mean of 1.0 and the pruning factor, and is applied for AA-NA pairs.
84     */
85    private final double nucleicPruningFactor;
86  
87    private final double nucleicPairsPruningFactor;
88    /**
89     * Pair clash energy threshold (kcal/mol) for MultiResidues.
90     */
91    private final double multiResPairClashAddn;
92    /**
93     * Flag to prune clashes.
94     */
95    private final boolean pruneClashes;
96    /**
97     * Flag to prune pair clashes.
98     */
99    private final boolean prunePairClashes;
100   /**
101    * Flag to control the verbosity of printing.
102    */
103   private final boolean print;
104   /**
105    * Eliminated rotamers. [residue][rotamer]
106    */
107   public boolean[][] eliminatedSingles;
108   /**
109    * Eliminated rotamer pairs. [residue1][rotamer1][residue2][rotamer2]
110    */
111   public boolean[][][][] eliminatedPairs;
112   /**
113    * Pruned rotamers. Only for JUnit testing purposes.
114    */
115   public boolean[][] onlyPrunedSingles;
116   /**
117    * Pruned rotamer pairs. Only for JUnit testing purposes.
118    */
119   public boolean[][][][] onlyPrunedPairs;
120 
121   private EnergyExpansion eE;
122 
123   public EliminatedRotamers(RotamerOptimization rO, DistanceMatrix dM, List<Residue> allResiduesList,
124                             int maxRotCheckDepth, double clashThreshold, double pairClashThreshold,
125                             double multiResClashThreshold, double nucleicPruningFactor, double nucleicPairsPruningFactor,
126                             double multiResPairClashAddn, boolean pruneClashes, boolean prunePairClashes, boolean print,
127                             Residue[] residues) {
128     this.rO = rO;
129     this.dM = dM;
130     this.allResiduesList = allResiduesList;
131     this.maxRotCheckDepth = maxRotCheckDepth;
132     this.clashThreshold = clashThreshold;
133     this.pairClashThreshold = pairClashThreshold;
134     this.multiResClashThreshold = multiResClashThreshold;
135     this.nucleicPruningFactor = nucleicPruningFactor;
136     this.nucleicPairsPruningFactor = nucleicPairsPruningFactor;
137     this.multiResPairClashAddn = multiResPairClashAddn;
138     this.pruneClashes = pruneClashes;
139     this.prunePairClashes = prunePairClashes;
140     this.print = print;
141     allocateEliminationMemory(residues);
142   }
143 
144   /**
145    * Check for eliminated rotamer; true if eliminated.
146    *
147    * @param i  Residue i.
148    * @param ri Rotamer ri.
149    * @return True if rotamer eliminated.
150    */
151   public boolean check(int i, int ri) {
152     if (eliminatedSingles == null) {
153       return false;
154     }
155     return eliminatedSingles[i][ri];
156   }
157 
158   /**
159    * Check for eliminated rotamer pair; true if eliminated.
160    *
161    * @param i  Residue i.
162    * @param ri Rotamer ri.
163    * @param j  Residue j.
164    * @param rj Rotamer rj.
165    * @return True if eliminated pair.
166    */
167   public boolean check(int i, int ri, int j, int rj) {
168     if (eliminatedPairs == null) {
169       return false;
170     }
171     // If j is an earlier residue than i, swap j with i, as eliminated
172     // rotamers are stored with the earlier residue listed first.
173     if (j < i) {
174       int ii = i;
175       int iri = ri;
176       i = j;
177       ri = rj;
178       j = ii;
179       rj = iri;
180     }
181     return eliminatedPairs[i][ri][j][rj];
182   }
183 
184   /**
185    * Check for pruned rotamer pair; true if eliminated. Only used during testing.
186    *
187    * @param i  Residue i.
188    * @param ri Rotamer ri.
189    * @param j  Residue j.
190    * @param rj Rotamer rj.
191    * @return a boolean.
192    */
193   public boolean checkPrunedPairs(int i, int ri, int j, int rj) {
194     if (onlyPrunedPairs == null) {
195       return false;
196     }
197 
198     if (j < i) {
199       int ii = i;
200       int iri = ri;
201       i = j;
202       ri = rj;
203       j = ii;
204       rj = iri;
205     }
206     return onlyPrunedPairs[i][ri][j][rj];
207   }
208 
209   /**
210    * Check for pruned rotamer; true if eliminated. Only used during testing.
211    *
212    * @param i  The residue.
213    * @param ri The rotamer.
214    * @return a boolean.
215    */
216   public boolean checkPrunedSingles(int i, int ri) {
217     if (onlyPrunedSingles == null) {
218       return false;
219     }
220     return onlyPrunedSingles[i][ri];
221   }
222 
223   /**
224    * Checks to see if any eliminations with j,rj have occurred; assumes i,ri self has already been
225    * checked. Checks j,rj self and i,ri,j,rj 2-Body. The intent is to be part of a loop over
226    * i,ri,j,rj, and check for eliminations at the j,rj point.
227    *
228    * @param i  Residue i
229    * @param ri Rotamer ri
230    * @param j  Residue j
231    * @param rj Rotamer rj
232    * @return j eliminated with i
233    */
234   public boolean checkToJ(int i, int ri, int j, int rj) {
235     return (check(j, rj) || check(i, ri, j, rj));
236   }
237 
238   /**
239    * Checks to see if any eliminations with k,rk have occurred; assumes i,ri,j,rj 2-Body has already
240    * been checked. Checks the k,rk self, all pairs with k,rk, and the i,ri,j,rj,k,rk 3-Body. The
241    * intent is to be part of a loop over i,ri,j,rj,k,rk, and check for eliminations at the k,rk
242    * point.
243    *
244    * @param i  Residue i
245    * @param ri Rotamer ri
246    * @param j  Residue j
247    * @param rj Rotamer rj
248    * @param k  Residue k
249    * @param rk Rotamer rk
250    * @return k eliminated with i,j
251    */
252   public boolean checkToK(int i, int ri, int j, int rj, int k, int rk) {
253     return (check(k, rk) || check(i, ri, k, rk) || check(j, rj, k, rk));
254   }
255 
256   /**
257    * Checks to see if any eliminations with l,rl have occurred; assumes i,ri,j,rj,k,rk 3-Body has
258    * already been checked. Checks the l,rl self, all pairs with l,rl, all triples with l,rl, and the
259    * 4-Body. The intent is to be part of a loop over i,ri,j,rj,k,rk,l,rl, and check for eliminations
260    * at the l,rl point.
261    *
262    * @param i  Residue i
263    * @param ri Rotamer ri
264    * @param j  Residue j
265    * @param rj Rotamer rj
266    * @param k  Residue k
267    * @param rk Rotamer rk
268    * @param l  Residue l
269    * @param rl Rotamer rl
270    * @return l eliminated with i,j,k
271    */
272   public boolean checkToL(int i, int ri, int j, int rj, int k, int rk, int l, int rl) {
273     return (check(l, rl) || check(i, ri, l, rl) || check(j, rj, l, rl) || check(k, rk, l, rl));
274   }
275 
276   /**
277    * Safe method to eliminate a rotamer: will not eliminate if there are no alternate rotamers for
278    * residue i, or if i-ri is already eliminated.
279    *
280    * @param residues Residues under consideration.
281    * @param i        A residue index based on the current residue list.
282    * @param ri       A rotamer to attempt elimination of.
283    * @param verbose  Request verbose logging.
284    * @return If the rotamer was eliminated.
285    */
286   public boolean eliminateRotamer(Residue[] residues, int i, int ri, boolean verbose) {
287     // Check if rotamer (i, ri) has already been eliminated.
288     if (check(i, ri)) {
289       return false;
290     }
291 
292     // Make sure at least one rotamer rii != ri is left.
293     int[] validRots = rotamerCount(residues, i);
294     int rotCount = 0;
295     for (int rii = 0; rii < validRots.length; rii++) {
296       if (rii != ri) {
297         ++rotCount;
298       }
299     }
300 
301     if (rotCount == 0) {
302       // No valid rotamers other than ri are left!
303       return false;
304     }
305 
306     eliminatedSingles[i][ri] = true;
307 
308     if (verbose) {
309       Rotamer[] rotamers = residues[i].getRotamers();
310       rO.logIfRank0(
311           format(" Rotamer (%8s,%2d) eliminated (%2d left).", residues[i].toString(rotamers[ri]), ri,
312               rotCount));
313     }
314     int eliminatedPairs = eliminateRotamerPairs(residues, i, ri, verbose);
315     if (eliminatedPairs > 0 && verbose) {
316       rO.logIfRank0(format("  Eliminated %2d rotamer pairs.", eliminatedPairs));
317     }
318     return true;
319   }
320 
321   public boolean eliminateRotamerPair(Residue[] residues, int i, int ri, int j, int rj,
322                                       boolean verbose) {
323     if (i > j) {
324       int ii = i;
325       int iri = ri;
326       i = j;
327       ri = rj;
328       j = ii;
329       rj = iri;
330     }
331 
332     if (!check(i, ri, j, rj)) {
333       eliminatedPairs[i][ri][j][rj] = true;
334       if (verbose) {
335         Rotamer[] rotI = residues[i].getRotamers();
336         Rotamer[] rotJ = residues[j].getRotamers();
337         rO.logIfRank0(format("  Rotamer pair eliminated: [(%8s,%2d) (%8s,%2d)]",
338             residues[i].toString(rotI[ri]), ri, residues[j].toString(rotJ[rj]), rj));
339       }
340       return true;
341     } else {
342       return false;
343     }
344   }
345 
346   public int eliminateRotamerPairs(Residue[] residues, int i, int ri, boolean verbose) {
347     int eliminatedPairs = 0;
348     for (int j = 0; j < residues.length; j++) {
349       if (j == i) {
350         continue;
351       }
352       Residue resJ = residues[j];
353       int lenRj = resJ.getRotamers().length;
354       for (int rj = 0; rj < lenRj; rj++) {
355         if (eliminateRotamerPair(residues, i, ri, j, rj, verbose)) {
356           ++eliminatedPairs;
357         }
358       }
359     }
360     return eliminatedPairs;
361   }
362 
363   /**
364    * Method to check if pairs elimination for some residue pair has enabled a singles rotamer
365    * elimination by eliminating all ri-rj for some ri or some rj.
366    *
367    * @param residues Residues under consideration.
368    * @param i        A residue index.
369    * @param j        A residue index j!=i
370    * @return If any singletons were eliminated.
371    */
372   public boolean pairsToSingleElimination(Residue[] residues, int i, int j) {
373     assert i != j;
374     assert i < residues.length;
375     assert j < residues.length;
376 
377     Residue residueI = residues[i];
378     Residue residueJ = residues[j];
379     Rotamer[] rotI = residueI.getRotamers();
380     Rotamer[] rotJ = residueJ.getRotamers();
381     int lenRi = rotI.length;
382     int lenRj = rotJ.length;
383     boolean eliminated = false;
384 
385     // Now check ris with no remaining pairs to j.
386     for (int ri = 0; ri < lenRi; ri++) {
387       if (check(i, ri)) {
388         continue;
389       }
390       boolean pairRemaining = false;
391       for (int rj = 0; rj < lenRj; rj++) {
392         if (!check(j, rj) && !check(i, ri, j, rj)) {
393           pairRemaining = true;
394           break;
395         }
396       }
397       if (!pairRemaining) {
398         if (eliminateRotamer(residues, i, ri, print)) {
399           eliminated = true;
400           rO.logIfRank0(format(" Eliminating rotamer %s-%d with no remaining pairs to residue %s.",
401               residueI.toString(rotI[ri]), ri, residueJ));
402         } else {
403           rO.logIfRank0(
404               format(" Already eliminated rotamer %s-%d with no remaining pairs to residue %s.",
405                   residueI.toString(rotI[ri]), ri, residueJ), Level.WARNING);
406         }
407       }
408     }
409 
410     // Check residue j rotamers with no remaining pairs to residue i.
411     for (int rj = 0; rj < lenRj; rj++) {
412       if (check(j, rj)) {
413         continue;
414       }
415       boolean pairRemaining = false;
416       for (int ri = 0; ri < lenRi; ri++) {
417         if (!check(i, ri) && !check(i, ri, j, rj)) {
418           pairRemaining = true;
419           break;
420         }
421       }
422       if (!pairRemaining) {
423         if (eliminateRotamer(residues, j, rj, print)) {
424           eliminated = true;
425           rO.logIfRank0(format(" Eliminating rotamer %s-%d with no remaining pairs to residue %s.",
426               residueJ.toString(rotJ[rj]), rj, residueI));
427         } else {
428           rO.logIfRank0(
429               format(" Already eliminated rotamer J %s-%d with no remaining pairs to residue %s.",
430                   residueJ.toString(rotJ[rj]), rj, residueI), Level.WARNING);
431         }
432       }
433     }
434 
435     return eliminated;
436   }
437 
438   /**
439    * Pre-prunes any pairs that have a pair-energy of Double.NaN before pruning and eliminations
440    * happen.
441    *
442    * @param residues Array of all residues.
443    */
444   public void prePrunePairs(Residue[] residues) {
445     // Pre-Prune if pair-energy is Double.NaN.
446     // Loop over first residue.
447     int nResidues = residues.length;
448     for (int i = 0; i < nResidues - 1; i++) {
449       Residue resi = residues[i];
450       Rotamer[] rotI = resi.getRotamers();
451       int ni = rotI.length;
452       // Loop over second residue.
453       for (int j = i + 1; j < nResidues; j++) {
454         Residue resJ = residues[j];
455         Rotamer[] rotJ = resJ.getRotamers();
456         int nj = rotJ.length;
457         // Loop over the rotamers for residue i.
458         for (int ri = 0; ri < ni; ri++) {
459           if (!validRotamer(residues, i, ri)) {
460             continue;
461           }
462           // Loop over rotamers for residue j.
463           for (int rj = 0; rj < nj; rj++) {
464             if (!validRotamer(residues, j, rj) || check(i, ri, j, rj)) {
465               continue;
466             }
467             if (!check(i, ri, j, rj) && Double.isNaN(eE.get2Body(i, ri, j, rj))) {
468               rO.logIfRank0(format(
469                   " Rotamer Pair (%7s,%2d) (%7s,%2d) 2-body energy %12.4f pre-pruned since energy is NaN.",
470                   i, ri, j, rj, eE.get2Body(i, ri, j, rj)));
471               eliminateRotamerPair(residues, i, ri, j, rj, print);
472             }
473           }
474         }
475       }
476     }
477   }
478 
479   /**
480    * Pre-prunes any selves that have a self-energy of Double.NaN before pruning and eliminations
481    * happen.
482    *
483    * @param residues Array of all residues.
484    */
485   public void prePruneSelves(Residue[] residues) {
486     // Pre-Prune if self-energy is Double.NaN.
487     for (int i = 0; i < residues.length; i++) {
488       Residue residue = residues[i];
489       Rotamer[] rotamers = residue.getRotamers();
490       int nRot = rotamers.length;
491       for (int ri = 0; ri < nRot; ri++) {
492         if (!check(i, ri) && Double.isNaN(eE.getSelf(i, ri))) {
493           rO.logIfRank0(
494               format(" Rotamer (%7s,%2d) self-energy %12.4f pre-pruned since energy is NaN.",
495                   residue, ri, eE.getSelf(i, ri)));
496           eliminateRotamer(residues, i, ri, false);
497         }
498       }
499     }
500   }
501 
502   /**
503    * Prunes rotamer ri of residue i if all ri-j pair energies are worse than the best i-j pair by
504    * some threshold value; additionally prunes ri-rj pairs if they exceed the best i-j pair by a
505    * greater threshold value; additionally performs this in reverse (searches over j-i).
506    *
507    * @param residues Residues whose rotamers are to be pruned.
508    */
509   public void prunePairClashes(Residue[] residues) {
510     if (!prunePairClashes) {
511       return;
512     }
513     int nResidues = residues.length;
514 
515     for (int i = 0; i < nResidues - 1; i++) {
516       Residue residueI = residues[i];
517       Rotamer[] rotI = residueI.getRotamers();
518       int lenRi = rotI.length;
519       int indI = allResiduesList.indexOf(residueI);
520       for (int j = i + 1; j < nResidues; j++) {
521         Residue residueJ = residues[j];
522         Rotamer[] rotJ = residueJ.getRotamers();
523         int lenRj = rotJ.length;
524         int indJ = allResiduesList.indexOf(residueJ);
525 
526         double minPair = Double.MAX_VALUE;
527         int minRI = -1;
528         int minRJ = -1;
529 
530         boolean cutoffPair = true;
531         for (int ri = 0; ri < lenRi; ri++) {
532           if (check(i, ri)) {
533             continue;
534           }
535           for (int rj = 0; rj < lenRj; rj++) {
536             if (check(j, rj) || check(i, ri, j, rj) || dM.checkPairDistThreshold(indI, ri, indJ,
537                 rj)) {
538               continue;
539             }
540             cutoffPair = false;
541             double pairEnergy = eE.get2Body(i, ri, j, rj) + eE.getSelf(i, ri) + eE.getSelf(j, rj);
542             assert Double.isFinite(pairEnergy);
543             if (pairEnergy < minPair) {
544               minPair = pairEnergy;
545               minRI = ri;
546               minRJ = rj;
547             }
548           }
549         }
550         if (cutoffPair) {
551           // Under this branch: no rotamer pairs that clear the distance threshold.
552           continue;
553         }
554         assert (minRI >= 0 && minRJ >= 0); // Otherwise, i and j are not on a well-packed backbone.
555 
556         // Calculate all the modifiers to the pair clash elimination threshold.
557         double threshold = pairClashThreshold;
558         if (residueI instanceof MultiResidue) {
559           threshold += multiResPairClashAddn;
560         }
561         if (residueJ instanceof MultiResidue) {
562           threshold += multiResPairClashAddn;
563         }
564         int numNARes =
565             (residueI.getResidueType() == NA ? 1 : 0) + (residueJ.getResidueType() == NA ? 1 : 0);
566         switch (numNARes) {
567           case 0:
568             break;
569           case 1:
570             threshold *= nucleicPairsPruningFactor;
571             break;
572           case 2:
573             threshold *= nucleicPruningFactor;
574             break;
575           default:
576             throw new ArithmeticException(" RotamerOptimization.prunePairClashes() has somehow "
577                 + "found less than zero or more than two nucleic acid residues in a pair of"
578                 + " residues. This result should be impossible.");
579         }
580         double toEliminate = threshold + minPair;
581 
582         for (int ri = 0; ri < lenRi; ri++) {
583           if (check(i, ri)) {
584             continue;
585           }
586           for (int rj = 0; rj < lenRj; rj++) {
587             if (check(j, rj) || check(i, ri, j, rj)) {
588               continue;
589             }
590             double pairEnergy = eE.get2Body(i, ri, j, rj) + eE.getSelf(i, ri) + eE.getSelf(j, rj);
591             assert Double.isFinite(pairEnergy);
592             if (pairEnergy > toEliminate) {
593               rO.logIfRank0(
594                   format(" Pruning pair %s-%d %s-%d by %s-%d %s-%d; energy %s > " + "%s + %s",
595                       residueI.toString(rotI[ri]), ri, residueJ.toString(rotJ[rj]), rj,
596                       residueI.toString(rotI[minRI]), minRI, residueJ.toString(rotJ[minRJ]), minRJ,
597                       rO.formatEnergy(pairEnergy), rO.formatEnergy(threshold),
598                       rO.formatEnergy(minPair)));
599             }
600           }
601         }
602 
603         pairsToSingleElimination(residues, i, j);
604       }
605     }
606   }
607 
608   /**
609    * Uses calculated energies to prune rotamers based on a threshold distance from that residue's
610    * minimum energy rotamer (by default 20 kcal/mol). The threshold can be modulated by presence of
611    * nucleic acids or MultiResidues, which require more generous pruning criteria.
612    *
613    * @param residues Residues to prune rotamers over.
614    */
615   public void pruneSingleClashes(Residue[] residues) {
616     if (!pruneClashes) {
617       return;
618     }
619     for (int i = 0; i < residues.length; i++) {
620       Residue residue = residues[i];
621       Rotamer[] rotamers = residue.getRotamers();
622       int nRot = rotamers.length;
623       double minEnergy = Double.MAX_VALUE;
624       int minRot = -1;
625       for (int ri = 0; ri < nRot; ri++) {
626         if (!check(i, ri) && eE.getSelf(i, ri) < minEnergy) {
627           minEnergy = eE.getSelf(i, ri);
628           minRot = ri;
629         }
630       }
631 
632       double energyToPrune =
633           (residue instanceof MultiResidue) ? multiResClashThreshold : clashThreshold;
634       energyToPrune =
635           (residue.getResidueType() == NA) ? energyToPrune * nucleicPruningFactor : energyToPrune;
636       energyToPrune += minEnergy;
637 
638       for (int ri = 0; ri < nRot; ri++) {
639         if (!check(i, ri) && (eE.getSelf(i, ri) > energyToPrune)) {
640           if (eliminateRotamer(residues, i, ri, print)) {
641             rO.logIfRank0(format("  Rotamer (%7s,%2d) self-energy %s pruned by (%7s,%2d) %s.",
642                 residue.toString(rotamers[ri]), ri, rO.formatEnergy(eE.getSelf(i, ri)),
643                 residue.toString(rotamers[minRot]), minRot, rO.formatEnergy(minEnergy)));
644           }
645         }
646       }
647     }
648   }
649 
650   public void setEnergyExpansion(EnergyExpansion eE) {
651     this.eE = eE;
652   }
653 
654   @Override
655   public String toString() {
656     int rotamerCount = 0;
657     int pairCount = 0;
658     int singles = 0;
659     int pairs = 0;
660     int nRes = eliminatedSingles.length;
661     for (int i = 0; i < nRes; i++) {
662       int nRotI = eliminatedSingles[i].length;
663       rotamerCount += nRotI;
664       for (int ri = 0; ri < nRotI; ri++) {
665         if (eliminatedSingles[i][ri]) {
666           singles++;
667         }
668         for (int j = i + 1; j < nRes; j++) {
669           int nRotJ = eliminatedPairs[i][ri][j].length;
670           pairCount += nRotJ;
671           for (int rj = 0; rj < nRotJ; rj++) {
672             if (eliminatedPairs[i][ri][j][rj]) {
673               pairs++;
674             }
675           }
676         }
677       }
678     }
679     return format(" %d out of %d rotamers eliminated.\n", singles, rotamerCount) + format(
680         " %d out of %d rotamer pairs eliminated.", pairs, pairCount);
681   }
682 
683   public boolean validateDEE(Residue[] residues) {
684     int nRes = eliminatedSingles.length;
685     // Validate residues
686     for (int i = 0; i < nRes; i++) {
687       Residue residueI = residues[i];
688       int ni = eliminatedSingles[i].length;
689       boolean valid = false;
690       for (int ri = 0; ri < ni; ri++) {
691         if (!check(i, ri)) {
692           valid = true;
693         }
694       }
695       if (!valid) {
696         logger.severe(
697             format(" Coding error: all %d rotamers for residue %s eliminated.", ni, residueI));
698       }
699     }
700 
701     // Validate pairs
702     for (int i = 0; i < nRes; i++) {
703       Residue residueI = residues[i];
704       Rotamer[] rotI = residueI.getRotamers();
705       int ni = rotI.length;
706       for (int j = i + 1; j < nRes; j++) {
707         Residue residueJ = residues[j];
708         Rotamer[] rotJ = residueJ.getRotamers();
709         int nj = rotJ.length;
710         boolean valid = false;
711         for (int ri = 0; ri < ni; ri++) {
712           for (int rj = 0; rj < nj; rj++) {
713             if (!check(i, ri, j, rj)) {
714               valid = true;
715             }
716           }
717         }
718         if (!valid) {
719           logger.severe(format(" Coding error: all pairs for %s with residue %s eliminated.",
720               residueI.toFormattedString(false, true), residueJ));
721         }
722       }
723     }
724 
725     return true;
726   }
727 
728   /**
729    * allocateEliminationMemory.
730    *
731    * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
732    */
733   private void allocateEliminationMemory(Residue[] residues) {
734     int nRes = residues.length;
735     eliminatedSingles = new boolean[nRes][];
736     eliminatedPairs = new boolean[nRes][][][];
737     // Loop over residues.
738     rO.logIfRank0("\n     Residue  Nrot");
739     for (int i = 0; i < nRes; i++) {
740       Residue residueI = residues[i];
741       Rotamer[] rotamersI = residueI.getRotamers();
742       int lenRi = rotamersI.length;
743       rO.logIfRank0(format(" %3d %8s %4d", i + 1, residueI.toFormattedString(false, true), lenRi));
744       eliminatedSingles[i] = new boolean[lenRi];
745       eliminatedPairs[i] = new boolean[lenRi][][];
746       // Loop over the set of rotamers for residue i.
747       for (int ri = 0; ri < lenRi; ri++) {
748         eliminatedSingles[i][ri] = false;
749         eliminatedPairs[i][ri] = new boolean[nRes][];
750         for (int j = i + 1; j < nRes; j++) {
751           Residue residueJ = residues[j];
752           Rotamer[] rotamersJ = residueJ.getRotamers();
753           int lenRj = rotamersJ.length;
754           eliminatedPairs[i][ri][j] = new boolean[lenRj];
755           for (int rj = 0; rj < lenRj; rj++) {
756             eliminatedPairs[i][ri][j][rj] = false;
757           }
758         }
759       }
760     }
761   }
762 
763   /**
764    * Validate residue i with rotamer ri.
765    *
766    * @param residues The residues being optimized.
767    * @param i        The residue to validate.
768    * @param ri       The rotamer to validate.
769    * @return The status of this rotamer.
770    */
771   private boolean validRotamer(Residue[] residues, int i, int ri) {
772     // Return false if this rotamer has been eliminated.
773     if (check(i, ri)) {
774       return false;
775     }
776 
777     if (maxRotCheckDepth > 1) {
778       // Loop over all residues to check for valid pairs and triples.
779       int n = residues.length;
780       for (int j = 0; j < n; j++) {
781         if (j == i) {
782           continue;
783         }
784         if (rotamerPairCount(residues, i, ri, j) == 0) {
785           return false;
786         }
787       }
788     }
789     return true;
790   }
791 
792   /**
793    * Count the rotamers remaining for residue i.
794    *
795    * @param residues Residue array.
796    * @param i        The residue number to examine.
797    * @return The remaining valid rotamers.
798    */
799   private int[] rotamerCount(Residue[] residues, int i) {
800     int nRes = residues.length;
801     Rotamer[] rotI = residues[i].getRotamers();
802     int ni = rotI.length;
803 
804     if (maxRotCheckDepth == 0) {
805       // Short-circuit on all its rotamers.
806       return IntStream.range(0, ni).toArray();
807     }
808 
809     return IntStream.range(0, ni).filter((int ri) -> {
810       if (check(i, ri)) {
811         return false;
812       }
813       if (maxRotCheckDepth > 1) {
814         // Check that rotamer ri has valid pairs with all other residues.
815         for (int j = 0; j < nRes; j++) {
816           if (i == j) {
817             continue;
818           }
819           if (rotamerPairCount(residues, i, ri, j) == 0) {
820             return false;
821           }
822         }
823       }
824       return true;
825     }).toArray();
826   }
827 
828   /**
829    * Validate rotamer pair (i, ri) and (j, rj).
830    *
831    * @param residues The residues being optimized.
832    * @param i        The first residue to validate.
833    * @param ri       The first rotamer to validate.
834    * @param j        The 2nd residue to validate.
835    * @param rj       The 2nd rotamer to validate.
836    * @return The status of this rotamer.
837    */
838   private boolean validRotamerPair(Residue[] residues, int i, int ri, int j, int rj) {
839     // Residues i and j must be different.
840     if (i == j) {
841       return false;
842     }
843 
844     // Return false if either rotamer is not valid.
845     if (!validRotamer(residues, i, ri) || !validRotamer(residues, j, rj)) {
846       return false;
847     }
848 
849     // Return false if the rotamer pair has been eliminated.
850     if (check(i, ri, j, rj)) {
851       return false;
852     }
853 
854     if (maxRotCheckDepth > 1) {
855       // Loop over all residues to check for valid triples.
856       int n = residues.length;
857       for (int k = 0; k < n; k++) {
858         if (k == i || k == j) {
859           continue;
860         }
861         // There must be at least one valid rotamer triple between (i, ri) and (j, rj) with residue
862         // k.
863         if (rotamerTripleCount(residues, i, ri, j, rj, k) == 0) {
864           // Eliminate the pair?
865           return false;
866         }
867       }
868     }
869     return true;
870   }
871 
872   /**
873    * Count the rotamer pairs remaining for (residue i, rotamer ri) and residue j.
874    *
875    * @param residues Residue array.
876    * @param i        The first residue to examine.
877    * @param ri       The rotamer for the first residue.
878    * @param j        The second residue to examine.
879    * @return The remaining rotamer pair count.
880    */
881   private int rotamerPairCount(Residue[] residues, int i, int ri, int j) {
882     if (i == j || check(i, ri)) {
883       return 0;
884     }
885     int pairCount = 0;
886     Rotamer[] rotJ = residues[j].getRotamers();
887     int nj = rotJ.length;
888     // Loop over all rotamers for residue j.
889     for (int rj = 0; rj < nj; rj++) {
890       // Check for a valid rotamer pair.
891       if (!check(j, rj) && !check(i, ri, j, rj)) {
892         // Loop over all residues k to check for valid rotamer triples.
893         int nRes = residues.length;
894         boolean valid = true;
895         if (maxRotCheckDepth > 2) {
896           for (int k = 0; k < nRes; k++) {
897             if (k == i || k == j) {
898               continue;
899             }
900             if (rotamerTripleCount(residues, i, ri, j, rj, k) == 0) {
901               valid = false;
902             }
903           }
904         }
905         if (valid) {
906           pairCount++;
907         }
908       }
909     }
910     return pairCount;
911   }
912 
913   /**
914    * Count the rotamer triples remaining for (residue i, rotamer ri) and (residue j, rotamer rj) with
915    * residue k.
916    *
917    * @param residues Residue array.
918    * @param i        The first residue to examine.
919    * @param ri       The rotamer for the first residue.
920    * @param j        The second residue to examine.
921    * @param rj       The rotamer for the first residue.
922    * @param k        The third residue.
923    * @return The remaining rotamer triples count.
924    */
925   private int rotamerTripleCount(Residue[] residues, int i, int ri, int j, int rj, int k) {
926     if (i == j || i == k || j == k) {
927       return 0;
928     }
929     int tripleCount = 0;
930     Rotamer[] rotK = residues[k].getRotamers();
931     int nk = rotK.length;
932     // Check that each rotamer and their pair have not been eliminated.
933     if (!check(i, ri) && !check(j, rj) && !check(i, ri, j, rj)) {
934       // Loop over all rotamers for residue k.
935       for (int rk = 0; rk < nk; rk++) {
936         // Check for a valid rotamer triple.
937         if (!check(k, rk) && !check(i, ri, k, rk) && !check(j, rj, k, rk)) {
938           tripleCount++;
939         }
940       }
941     }
942     return tripleCount;
943   }
944 }