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.cli;
39  
40  import static java.lang.Integer.parseInt;
41  
42  import ffx.algorithms.optimize.RotamerOptimization;
43  import ffx.algorithms.optimize.RotamerOptimization.Algorithm;
44  import ffx.numerics.math.DoubleMath;
45  import ffx.potential.MolecularAssembly;
46  import ffx.potential.bonded.*;
47  
48  import java.io.File;
49  import java.util.*;
50  import java.util.logging.Logger;
51  
52  import picocli.CommandLine.ArgGroup;
53  import picocli.CommandLine.Option;
54  
55  /**
56   * Represents command line options for scripts that use a many-body expansion for global
57   * optimization.
58   *
59   * @author Michael J. Schnieders
60   * @author Mallory R. Tollefson
61   * @since 1.0
62   */
63  public class ManyBodyOptions {
64  
65    private static final Logger logger = Logger.getLogger(ManyBodyOptions.class.getName());
66  
67    /**
68     * The ArgGroup keeps the ManyBodyOptionGroup together when printing help.
69     */
70    @ArgGroup(heading = "%n Many-Body Optimization Options%n", validate = false)
71    private final ManyBodyOptionGroup group = new ManyBodyOptionGroup();
72  
73    /**
74     * The ArgGroup keeps the BoxOptionGroup together when printing help.
75     */
76    @ArgGroup(heading = "%n Many-Body Box Optimization Options%n", validate = false)
77    private final BoxOptionGroup boxGroup = new BoxOptionGroup();
78  
79    /**
80     * The ArgGroup keeps the WindowOptionGroup together when printing help.
81     */
82    @ArgGroup(heading = "%n Many-Body Window Optimization Options%n", validate = false)
83    private final WindowOptionGroup windowGroup = new WindowOptionGroup();
84  
85    /**
86     * The ArgGroup keeps the WindowOptionGroup together when printing help.
87     */
88    @ArgGroup(heading = "%n Many-Body Energy Expansion and Cut-off Options%n", validate = false)
89    private final EnergyOptionGroup energyGroup = new EnergyOptionGroup();
90  
91    /**
92     * The ArgGroup keeps the ResidueOptionGroup together when printing help.
93     */
94    @ArgGroup(heading = "%n Many-Body Residue Selection Options%n", validate = false)
95    private final ResidueOptionGroup residueGroup = new ResidueOptionGroup();
96  
97    private RotamerOptimization rotamerOptimization;
98    private RotamerLibrary rotamerLibrary;
99  
100   /**
101    * initRotamerOptimization.
102    *
103    * @param rotamerOptimization a {@link RotamerOptimization} object.
104    * @param activeAssembly a {@link ffx.potential.MolecularAssembly} object.
105    */
106   public void initRotamerOptimization(RotamerOptimization rotamerOptimization,
107       MolecularAssembly activeAssembly) {
108     this.rotamerOptimization = rotamerOptimization;
109 
110     // Collect the residues to optimize.
111     List<Residue> residues = collectResidues(activeAssembly);
112     rotamerOptimization.setResidues(residues);
113     rotamerOptimization.setRotamerLibrary(rotamerLibrary);
114 
115     // If the user has not selected an algorithm, it will be chosen based on the number of residues.
116     Algorithm algorithm = getAlgorithm(residues.size());
117 
118     // Configure general options.
119     rotamerOptimization.setDecomposeOriginal(group.decompose);
120     rotamerOptimization.setUseGoldstein(!group.dee);
121     rotamerOptimization.setRevert(group.revert);
122     boolean monteCarloBool = group.monteCarlo > 1;
123     rotamerOptimization.setMonteCarlo(monteCarloBool, group.monteCarlo);
124     File energyRestartFile;
125     if (!group.energyRestart.equalsIgnoreCase("none")) {
126       energyRestartFile = new File(group.energyRestart);
127       rotamerOptimization.setEnergyRestartFile(energyRestartFile);
128     }
129 
130     // Configure Energy Expansion and Pruning Options.
131     rotamerOptimization.setTwoBodyCutoff(energyGroup.twoBodyCutoff);
132     rotamerOptimization.setThreeBodyEnergy(energyGroup.threeBody);
133     rotamerOptimization.setThreeBodyCutoff(energyGroup.threeBodyCutoff);
134     rotamerOptimization.setDistanceCutoff(energyGroup.cutoff);
135     rotamerOptimization.setPruning(energyGroup.prune);
136     rotamerOptimization.setSingletonClashThreshold(energyGroup.clashThreshold);
137     rotamerOptimization.setPairClashThreshold(energyGroup.pairClashThreshold);
138 
139     // Window
140     if (algorithm == Algorithm.WINDOW) {
141       rotamerOptimization.setWindowSize(windowGroup.window);
142       rotamerOptimization.setIncrement(windowGroup.increment);
143     } else if (algorithm == Algorithm.BOX) {
144       // Box
145       parseBoxSelection();
146       if (boxGroup.approxBoxLength < 0) {
147         logger.info(" Negative box length value changed to -1 * input.");
148         boxGroup.approxBoxLength *= -1;
149       }
150       rotamerOptimization.setBoxBorderSize(boxGroup.boxBorderSize);
151       rotamerOptimization.setApproxBoxLength(boxGroup.approxBoxLength);
152       rotamerOptimization.setNumXYZBoxes(boxGroup.numXYZBoxes);
153       rotamerOptimization.setBoxInclusionCriterion(boxGroup.boxInclusionCriterion);
154       rotamerOptimization.setBoxStart(boxGroup.initialBox);
155       rotamerOptimization.setBoxEnd(boxGroup.finalBox);
156     }
157   }
158 
159   /**
160    * Collect residues based on residue selection flags.
161    *
162    * @param activeAssembly a {@link ffx.potential.MolecularAssembly} object.
163    */
164   public List<Residue> collectResidues(MolecularAssembly activeAssembly) {
165 
166     // Force re-initialization RotamerLibrary prior to collecting residues and rotamers.
167     initRotamerLibrary(true);
168 
169     // First, interpret the residueGroup.listResidues flag if its set.
170     if (!residueGroup.listResidues.isEmpty() && !residueGroup.listResidues.equalsIgnoreCase("none")) {
171       List<String> stringList = new ArrayList<>();
172       String[] tok = residueGroup.listResidues.split(",");
173       Collections.addAll(stringList, tok);
174       List<Residue> residueList = new ArrayList<>();
175       Polymer[] polymers = activeAssembly.getChains();
176       for (String s : stringList) {
177         Character chainID = s.charAt(0);
178         int i = parseInt(s.substring(1));
179         for (Polymer polymer : polymers) {
180           if (polymer.getChainID() == chainID) {
181             List<Residue> residues = polymer.getResidues();
182             for (Residue residue : residues) {
183               if (residue.getResidueNumber() == i) {
184                 Rotamer[] rotamers = residue.setRotamers(rotamerLibrary);
185                 if (rotamers != null && rotamers.length > 0) {
186                   residueList.add(residue);
187                 }
188               }
189             }
190           }
191         }
192       }
193       return residueList;
194     }
195 
196     // Check that the finish flag is greater than the start flag.
197     if (residueGroup.finish < residueGroup.start) {
198       residueGroup.finish = Integer.MAX_VALUE;
199     }
200     Character chainID = null;
201     if (!residueGroup.chain.equalsIgnoreCase("-1")) {
202       chainID = residueGroup.chain.charAt(0);
203     }
204 
205     // Otherwise, collect all residues with a rotamer.
206     List<Residue> residueList = new ArrayList<>();
207     Polymer[] polymers = activeAssembly.getChains();
208     for (Polymer polymer : polymers) {
209       // Enforce requested chainID.
210       if (chainID != null && chainID != polymer.getChainID()) {
211         continue;
212       }
213       List<Residue> residues = polymer.getResidues();
214       for (Residue residue : residues) {
215         int resID = residue.getResidueNumber();
216         // Enforce requested residue range.
217         if (resID >= residueGroup.start && resID <= residueGroup.finish) {
218           Rotamer[] rotamers = residue.setRotamers(rotamerLibrary);
219           if (rotamers != null) {
220             residueList.add(residue);
221           }
222         }
223       }
224     }
225 
226     return residueList;
227   }
228 
229   /**
230    * Returns the user selected algorithm or one chosen based on number of residues.
231    *
232    * @return Returns the algorithm choice.
233    */
234   public Algorithm getAlgorithm(int numResidues) {
235     if (group.algorithm == 0) {
236       if (numResidues < 100) {
237         return Algorithm.ALL;
238       } else {
239         return Algorithm.BOX;
240       }
241     }
242     return Algorithm.getAlgorithm(group.algorithm);
243   }
244 
245   public boolean getUsingOriginalCoordinates() {
246     return !group.noOriginal;
247   }
248 
249   public void setOriginalCoordinates(boolean useOrig) {
250     group.noOriginal = !useOrig;
251   }
252 
253   public double getApproximate() {
254     return rotamerOptimization.getApproximate();
255   }
256 
257   /**
258    * Gets the restart file created during rotamer optimization.
259    *
260    * @return The restart file.
261    */
262   public File getRestartFile() {
263     return rotamerOptimization.getRestartFile();
264   }
265 
266   public RotamerLibrary getRotamerLibrary(boolean reinit) {
267     initRotamerLibrary(reinit);
268     return rotamerLibrary;
269   }
270 
271   private void initRotamerLibrary(boolean reinit) {
272     if (rotamerLibrary == null || reinit) {
273       boolean useOrigCoordsRotamer = !group.noOriginal;
274       if (group.decompose) {
275         useOrigCoordsRotamer = true;
276       }
277       rotamerLibrary = new RotamerLibrary(
278           RotamerLibrary.ProteinLibrary.intToProteinLibrary(group.library), useOrigCoordsRotamer);
279     }
280   }
281 
282 
283   /** Set allStartResID, boxStart and boxEnd */
284   private void parseBoxSelection() {
285     // Parse the numBoxes flag.
286     String input = boxGroup.numBoxes;
287     Scanner boxNumInput = new java.util.Scanner(input);
288     boxNumInput.useDelimiter(",");
289     int inputLoopCounter = 0;
290     int[] numXYZBoxes = new int[3];
291     numXYZBoxes[0] = 3; // Default
292     while (inputLoopCounter < 3) {
293       if (boxNumInput.hasNextInt()) {
294         numXYZBoxes[inputLoopCounter] = boxNumInput.nextInt();
295         inputLoopCounter++;
296       } else if (boxNumInput.hasNextDouble()) {
297         numXYZBoxes[inputLoopCounter] = (int) Math.floor(boxNumInput.nextDouble());
298         inputLoopCounter++;
299         logger.info(" Double input to nB truncated to integer.");
300       } else if (boxNumInput.hasNext()) {
301         logger.info(" Non-numeric input to nB discarded");
302         boxNumInput.next();
303       } else {
304         logger.info(
305             " Insufficient input to nB. Non-input values assumed either equal to X or default to 3");
306         break;
307       }
308     }
309     boxNumInput.close();
310 
311     // Initialize dimensions not provided.
312     for (int i = inputLoopCounter; i < 3; i++) {
313       numXYZBoxes[i] = numXYZBoxes[0];
314     }
315 
316     // Correct input errors.
317     int totalCount = 1;
318     for (int i = 0; i < 3; i++) {
319       if (numXYZBoxes[i] == 0) {
320         numXYZBoxes[i] = 3;
321         logger.info(" Input of 0 to nB reset to default of 3.");
322       } else if (numXYZBoxes[i] < 0) {
323         numXYZBoxes[i] = -1 * numXYZBoxes[i];
324         logger.info(" Input of negative number to nB reset to positive number");
325       }
326       totalCount *= numXYZBoxes[i];
327     }
328 
329     boxGroup.numXYZBoxes = numXYZBoxes;
330 
331     if (boxGroup.initialBox < 0) {
332       boxGroup.initialBox = 0;
333     }
334     if (boxGroup.finalBox < 0 || boxGroup.finalBox > totalCount) {
335       boxGroup.finalBox = totalCount;
336     }
337 
338   }
339 
340   /** Sets the standard values for properties in rotamer optimization. */
341   private void setRotOptProperties(Algorithm algorithm) {
342 
343   }
344 
345   public void setAlgorithm(int algorithm) {
346     group.algorithm = algorithm;
347   }
348 
349   /**
350    * Ponder and Richards (1) or Richardson (2) rotamer library.
351    *
352    * @return Returns the Rotamer library.
353    */
354   public int getLibrary() {
355     return group.library;
356   }
357 
358   public void setLibrary(int library) {
359     group.library = library;
360   }
361 
362   /**
363    * Nucleic acid library: currently only Richardson available.
364    *
365    * @return Returns a String for the nucleic acid library.
366    */
367   public String getNaLibraryName() {
368     return group.naLibraryName;
369   }
370 
371   public void setNaLibraryName(String naLibraryName) {
372     group.naLibraryName = naLibraryName;
373   }
374 
375   /**
376    * Use dead-end elimination criteria instead of Goldstein criteria.
377    *
378    * @return Returns true if using DEE instead of Goldstein.
379    */
380   public boolean isDee() {
381     return group.dee;
382   }
383 
384   public void setDee(boolean dee) {
385     group.dee = dee;
386   }
387 
388   /**
389    * Single character chain ID of the residues to optimize.
390    *
391    * @return Returns the Chain name.
392    */
393   public String getChain() {
394     return residueGroup.chain;
395   }
396 
397   public void setChain(String chain) {
398     residueGroup.chain = chain;
399   }
400 
401   public boolean getOnlyTitration() {
402     return residueGroup.onlyTitration;
403   }
404 
405   public void setOnlyTitration(boolean onlyTitration) {
406     residueGroup.onlyTitration = onlyTitration;
407   }
408 
409   public boolean getOnlyProtons() {
410     return residueGroup.onlyProtons;
411   }
412 
413   public void setOnlyProtons(boolean onlyProtons) {
414     residueGroup.onlyProtons = onlyProtons;
415   }
416 
417   public int getInterestedResidue() {
418     return residueGroup.interestedResidue;
419   }
420 
421   public void setInterestedResidue(int interestedResidue) {
422     residueGroup.interestedResidue = interestedResidue;
423   }
424 
425   public double getInclusionCutoff() {
426     return residueGroup.inclusionCutoff;
427   }
428 
429   public void setInclusionCutoff(double inclusionCutoff) {
430     residueGroup.inclusionCutoff = inclusionCutoff;
431   }
432 
433   /**
434    * Starting residue to perform the optimization on (-1 exits). For box optimization, first box to
435    * optimize.
436    *
437    * @return Returns the starting index.
438    */
439   public int getStart() {
440     return residueGroup.start;
441   }
442 
443   public void setStart(int start) {
444     residueGroup.start = start;
445   }
446 
447   /**
448    * Final residue to perform the optimization on (-1 exits). For box optimization, final box to
449    * optimize.
450    *
451    * @return Returns the finish index.
452    */
453   public int getFinish() {
454     return residueGroup.finish;
455   }
456 
457   public void setFinish(int finish) {
458     residueGroup.finish = finish;
459   }
460 
461   /**
462    * Cutoff distance for two-body interactions.
463    *
464    * @return Returns the 2-body cutoff.
465    */
466   public double getTwoBodyCutoff() {
467     return energyGroup.twoBodyCutoff;
468   }
469 
470   public void setTwoBodyCutoff(double twoBodyCutoff) {
471     energyGroup.twoBodyCutoff = twoBodyCutoff;
472   }
473 
474   /**
475    * -T or --threeBody Include 3-Body interactions in the elimination criteria.
476    *
477    * @return Returns true if 3-body interactions are being used.
478    */
479   public boolean isThreeBody() {
480     return energyGroup.threeBody;
481   }
482 
483   public void setThreeBody(boolean threeBody) {
484     energyGroup.threeBody = threeBody;
485   }
486 
487   /**
488    * Cutoff distance for three-body interactions.
489    *
490    * @return Returns the 3-body cutoff.
491    */
492   public double getThreeBodyCutoff() {
493     return energyGroup.threeBodyCutoff;
494   }
495 
496   public void setThreeBodyCutoff(double threeBodyCutoff) {
497     energyGroup.threeBodyCutoff = threeBodyCutoff;
498   }
499 
500   /**
501    * Prune no clashes (0), only single clashes (1), or all clashes (2).
502    *
503    * @return Returns the pruning condition.
504    */
505   public int getPrune() {
506     return energyGroup.prune;
507   }
508 
509   public void setPrune(int prune) {
510     energyGroup.prune = prune;
511   }
512 
513   /**
514    * Revert unfavorable changes.
515    *
516    * @return Returns true if unfavorable changes are reverted.
517    */
518   public boolean isRevert() {
519     return group.revert;
520   }
521 
522   public void setRevert(boolean revert) {
523     group.revert = revert;
524   }
525 
526   /**
527    * Energy restart file from a previous run (requires that all parameters are the same).
528    *
529    * @return Returns the energy restart file to use.
530    */
531   public String getEnergyRestart() {
532     return group.energyRestart;
533   }
534 
535   public void setEnergyRestart(String energyRestart) {
536     group.energyRestart = energyRestart;
537   }
538 
539   /**
540    * Do not include starting coordinates as their own rotamer.
541    *
542    * @return Returns true if original side-chain coordinates should not be used as a rotamer.
543    */
544   public boolean isNoOriginal() {
545     return group.noOriginal;
546   }
547 
548   public void setNoOriginal(boolean noOriginal) {
549     group.noOriginal = noOriginal;
550   }
551 
552   /**
553    * -E or --decompose Print energy decomposition for the input structure (no optimization).
554    *
555    * @return Returns true if the input structure should undergo an energy decomposition.
556    */
557   public boolean isDecompose() {
558     return group.decompose;
559   }
560 
561   public void setDecompose(boolean decompose) {
562     group.decompose = decompose;
563   }
564 
565   /**
566    * Choose a list of individual residues to optimize (eg. A11,A24,B40).
567    *
568    * @return Returns the list of selected residues.
569    */
570   public String getListResidues() {
571     return residueGroup.listResidues;
572   }
573 
574   public void setListResidues(String listResidues) {
575     residueGroup.listResidues = listResidues;
576   }
577 
578   /**
579    * Follow elimination criteria with 'n' Monte Carlo steps, or enumerate all remaining
580    * conformations, whichever is smaller.
581    *
582    * @return Returns the number of Monte Carlo optimization steps to apply.
583    */
584   public int getMonteCarlo() {
585     return group.monteCarlo;
586   }
587 
588   public void setMonteCarlo(int monteCarlo) {
589     group.monteCarlo = monteCarlo;
590   }
591 
592   /**
593    * Save eliminated singles and eliminated pairs to a text file (global and box optimization).
594    *
595    * @return Returns true to Save eliminated rotamers to a file.
596    */
597   public boolean isSaveOutput() {
598     return group.saveOutput;
599   }
600 
601   public void setSaveOutput(boolean saveOutput) {
602     group.saveOutput = saveOutput;
603   }
604 
605   /**
606    * Size of the sliding window with respect to adjacent residues (default = 7).
607    *
608    * @return Returns the sliding window size.
609    */
610   public int getWindow() {
611     return windowGroup.window;
612   }
613 
614   public void setWindow(int window) {
615     windowGroup.window = window;
616   }
617 
618   /**
619    * Sliding window increment (default = 3).
620    *
621    * @return Returns the sliding window increment.
622    */
623   public int getIncrement() {
624     return windowGroup.increment;
625   }
626 
627   public void setIncrement(int increment) {
628     windowGroup.increment = increment;
629   }
630 
631   /**
632    * The sliding window and box cutoff radius (Angstroms).
633    *
634    * @return Returns the sliding window cutoff radius.
635    */
636   public double getCutoff() {
637     return energyGroup.cutoff;
638   }
639 
640   public void setCutoff(double cutoff) {
641     energyGroup.cutoff = cutoff;
642   }
643 
644   /**
645    * The threshold for pruning clashes. If two self-energies on the same residue have an energy
646    * difference greater than 25 kcal/mol, the high energy rotamers get pruned.
647    *
648    * @return Returns the clash threshold for self energies.
649    */
650   public double getClashThreshold() {
651     return energyGroup.clashThreshold;
652   }
653 
654   public void setClashThreshold(double clashThreshold) {
655     energyGroup.clashThreshold = clashThreshold;
656   }
657 
658   /**
659    * The threshold for pruning clashes. If two pair-energies on the same residues have an energy
660    * difference greater than 25 kcal/mol, the high energy rotamers get pruned.
661    *
662    * @return Returns the clash threshold for pair energies.
663    */
664   public double getPairClashThreshold() {
665     return energyGroup.pairClashThreshold;
666   }
667 
668   public void setPairClashThreshold(double pairClashThreshold) {
669     energyGroup.pairClashThreshold = pairClashThreshold;
670   }
671 
672   /**
673    * The number of boxes along X, Y, and Z (default: '3,3,3').
674    *
675    * @return Returns the number of boxes.
676    */
677   public String getNumBoxes() {
678     return boxGroup.numBoxes;
679   }
680 
681   public void setNumBoxes(String numBoxes) {
682     boxGroup.numBoxes = numBoxes;
683   }
684 
685   /**
686    * Extent of overlap between optimization boxes (default: 0.0 A).
687    *
688    * @return Returns the overlap between optimization boxes.
689    */
690   public double getBoxBorderSize() {
691     return boxGroup.boxBorderSize;
692   }
693 
694   public void setBoxBorderSize(double boxBorderSize) {
695     boxGroup.boxBorderSize = boxBorderSize;
696   }
697 
698   /**
699    * Approximate side lengths of boxes to be constructed (over-rides numXYZBoxes). Box sizes are
700    * rounded up to make a whole number of boxes along each axis (default of 0 disables this
701    * function).
702    *
703    * @return Returns the approximate box length.
704    */
705   public double getApproxBoxLength() {
706     return boxGroup.approxBoxLength;
707   }
708 
709   public void setApproxBoxLength(double approxBoxLength) {
710     boxGroup.approxBoxLength = approxBoxLength;
711   }
712 
713   /**
714    * Criterion to use for adding residues to boxes. (1) uses C alpha only (N1/9 for nucleic acids)
715    * (2) uses any atom. (3) uses any rotamer.
716    *
717    * @return Returns the Box inclusion criteria.
718    */
719   public int getBoxInclusionCriterion() {
720     return boxGroup.boxInclusionCriterion;
721   }
722 
723   public void setBoxInclusionCriterion(int boxInclusionCriterion) {
724     boxGroup.boxInclusionCriterion = boxInclusionCriterion;
725   }
726 
727   public void setTitrationPH(double pH) {
728     group.titrationPH = pH;
729   }
730 
731   public double getTitrationPH() {
732     return group.titrationPH;
733   }
734 
735   public void setPHRestraint(double pHRestraint) {
736     energyGroup.pHRestraint = pHRestraint;
737   }
738 
739   public double getPHRestraint() {
740     return energyGroup.pHRestraint;
741   }
742 
743   public boolean isTitrating() {
744     return group.titrationPH == 0;
745   }
746 
747   public boolean getTitration() {
748     return group.titration;
749   }
750 
751   public String selectInclusionResidues(final List<Residue> residueList, int mutatingResidue, boolean onlyTitration, boolean onlyProtons,
752                                        double inclusionCutoff){
753     String listResidues = "";
754     if (mutatingResidue != -1 && inclusionCutoff != -1) {
755       List<Integer> residueNumber = new ArrayList<>();
756       for (Residue residue : residueList) {
757         residueNumber.add(residue.getResidueNumber());
758       }
759       double[] mutatingResCoor = new double[3];
760       int index = residueNumber.indexOf(mutatingResidue);
761       mutatingResCoor = residueList.get(index).getAtomByName("CA", true).getXYZ(mutatingResCoor);
762       for (Residue residue: residueList) {
763         double[] currentResCoor = new double[3];
764         currentResCoor = residue.getAtomByName("CA", true).getXYZ(currentResCoor);
765         double dist = DoubleMath.dist(mutatingResCoor, currentResCoor);
766         if (dist < inclusionCutoff) {
767           listResidues += "," + residue.getChainID() + residue.getResidueNumber();
768         }
769       }
770       listResidues = listResidues.substring(1);
771     } else if (onlyTitration || onlyProtons){
772       String[] titratableResidues = new String[]{"HIS", "HIE", "HID", "GLU", "GLH", "ASP", "ASH", "LYS", "LYD", "CYS", "CYD"};
773       List<String> titratableResiudesList = Arrays.asList(titratableResidues);
774       for (Residue residue : residueList) {
775         if (titratableResiudesList.contains(residue.getName())) {
776           String titrateResNum = Integer.toString(residue.getResidueNumber());
777           if(!listResidues.contains(titrateResNum)){
778             listResidues += "," + residue.getChainID()+ titrateResNum;
779           }
780           if (inclusionCutoff != -1){
781             for (Residue residue2: residueList) {
782               boolean includeResidue = evaluateAllRotDist(residue, residue2, inclusionCutoff);
783               if(includeResidue){
784                 String residue2Number = Integer.toString(residue2.getResidueNumber());
785                 if(!listResidues.contains(residue2Number)){
786                   listResidues += "," + residue2.getChainID()+ residue2Number;
787                 }
788               }
789             }
790           }
791 
792         }
793 
794       }
795 
796       listResidues = listResidues.substring(1);
797     }
798     return listResidues;
799   }
800   private static boolean evaluateAllRotDist(Residue residueA, Residue residueB, double inclusionCutoff){
801     residueA.setRotamers(RotamerLibrary.getDefaultLibrary());
802     residueB.setRotamers(RotamerLibrary.getDefaultLibrary());
803     Rotamer[] rotamersA = residueA.getRotamers();
804     Rotamer[] rotamersB = residueB.getRotamers();
805     double[] aCoor = new double[3];
806     double[] bCoor = new double[3];
807     try {
808       int a = rotamersA.length;
809       int b = rotamersB.length;
810     } catch (Exception e){
811       return false;
812     }
813 
814     for(Rotamer rotamerA: rotamersA){
815       residueA.setRotamer(rotamerA);
816       for(Rotamer rotamerB: rotamersB){
817         residueB.setRotamer(rotamerB);
818         for(Atom atomA: residueA.getAtomList()){
819           for(Atom atomB: residueB.getAtomList()){
820             double dist = DoubleMath.dist(atomA.getXYZ(aCoor), atomB.getXYZ(bCoor));
821             if(dist <= inclusionCutoff){
822               return true;
823             }
824           }
825         }
826       }
827     }
828 
829     return false;
830   }
831 
832 
833   /**
834    * Collection of ManyBody Options.
835    */
836   private static class ManyBodyOptionGroup {
837 
838     /**
839      * -a or --algorithm Choices are independent residues (1), all with rotamer elimination (2), all
840      * brute force (3), sliding window (4), or box optimization (5).
841      */
842     @Option(names = {"-a",
843         "--algorithm"}, paramLabel = "0", defaultValue = "0", description = "Algorithm: default automatic settings (0), independent residues (1), all with rotamer elimination (2), all brute force (3), sliding window (4), or box optimization (5)")
844     private int algorithm;
845 
846     /** -L or --library Choose either Ponder and Richards (1) or Richardson (2) rotamer library. */
847     @Option(names = {"-L",
848         "--library"}, paramLabel = "2", defaultValue = "2", description = "Ponder and Richards (1) or Richardson (2) rotamer library.")
849     private int library;
850 
851     /** -nl or --nucleicLibrary Choose a nucleic acid library: currently only Richardson available. */
852     // @Option(
853     //    names = {"--nl", "--nucleiclibrary"},
854     //    paramLabel = "Richardson",
855     //    defaultValue = "Richardson",
856     //    description = "Nucleic acid library to select: [Richardson]")
857     private String naLibraryName = "Richardson";
858 
859     /** --dee or --deadEnd Use dead-end elimination criteria instead of Goldstein criteria. */
860     @Option(names = {"--dee",
861         "--deadEnd"}, paramLabel = "false", defaultValue = "false", description = "Use dead-end elimination criteria instead of Goldstein criteria.")
862     private boolean dee;
863 
864     /** -z or --revert Revert unfavorable changes. */
865     @Option(names = {"-z",
866         "--revert"}, defaultValue = "true", description = "Revert unfavorable changes.")
867     private boolean revert;
868 
869     /**
870      * --eR or --energyRestart Load energy restart file from a previous run (requires that all
871      * parameters are the same).
872      */
873     @Option(names = {"--eR",
874         "--energyRestart"}, paramLabel = "none", defaultValue = "none", description = "Load energy restart file from a previous run (requires that all parameters are the same).")
875     private String energyRestart;
876 
877     /** -O or --noOriginal Do not include starting coordinates as their own rotamer. */
878     @Option(names = {"-O",
879         "--noOriginal"}, defaultValue = "false", description = "Do not include starting coordinates as their own rotamer.")
880     private boolean noOriginal;
881 
882     /** -E or --decompose Print energy decomposition for the input structure (no optimization). */
883     @Option(names = {"-E",
884         "--decompose"}, defaultValue = "false", description = "Print energy decomposition for the input structure (no optimization!).")
885     private boolean decompose;
886 
887     /**
888      * --pH or --titrationPH Optimize the titration state of ASP, GLU, HIS and LYS residues.
889      */
890     @Option(names = {"--pH",
891         "--titrationPH"}, paramLabel = "0", defaultValue = "0", description = " Optimize the titration state of ASP, GLU, HIS and LYS residues at the given pH (pH = 0 turns off titration")
892     private double titrationPH;
893 
894     /**
895      * --pH or --titrationPH Optimize the titration state of ASP, GLU, HIS and LYS residues.
896      */
897     @Option(names = {"--tR",
898             "--titration"}, paramLabel = "false", defaultValue = "false", description = " Turn on titration state optimization")
899     private boolean titration;
900 
901     /**
902      * --mC or --monteCarlo Follow elimination criteria with 'n' Monte Carlo steps, or enumerate all
903      * remaining conformations, whichever is smaller.
904      */
905     @Option(names = {"--mC",
906         "--monteCarlo"}, paramLabel = "-1", defaultValue = "-1", description = "Follow elimination criteria with (n) Monte Carlo steps, or enumerate all remaining conformations, whichever is smaller.")
907     private int monteCarlo;
908 
909     /**
910      * -out or --output Save eliminated singles and eliminated pairs to a text file (global and box
911      * optimization).
912      */
913     //  @Option(
914     //      names = {"--out", "--output"},
915     //      defaultValue = "false",
916     //      description = "Save eliminated singles and eliminated pairs to a text file.")
917     private boolean saveOutput;
918 
919   }
920 
921   /**
922    * Collection of ManyBody Box Optimization Options.
923    */
924   private static class BoxOptionGroup {
925 
926     /** -nB or --numBoxes Specify number of boxes along X, Y, and Z (default: '3,3,3'). */
927     @Option(names = {"--nB",
928         "--numBoxes"}, paramLabel = "3,3,3", defaultValue = "3,3,3", description = "Specify number of boxes along X, Y, and Z (default: 3,3,3)")
929     private String numBoxes;
930 
931     /**
932      * Result of parsing numBoxes flag.
933      */
934     private int[] numXYZBoxes;
935 
936     /** -bB or --boxBorderSize Extent of overlap between optimization boxes (default: 0.0 A). */
937     @Option(names = {"--bB",
938         "--boxBorderSize"}, paramLabel = "0.0", defaultValue = "0.0", description = "Extent of overlap between optimization boxes in Angstroms.")
939     private double boxBorderSize;
940 
941     /**
942      * -bL or --approxBoxLength Approximate side lengths of boxes to be constructed (over-rides
943      * numXYZBoxes). Box sizes are rounded up to make a whole number of boxes along each axis
944      * (default of 0 disables this function).
945      */
946     @Option(names = {"--bL",
947         "--approxBoxLength"}, paramLabel = "20.0", defaultValue = "20.0", description = "Approximate side lengths of boxes to be constructed (over-rides numXYZBoxes).")
948     private double approxBoxLength;
949 
950     /**
951      * -bC or --boxInclusionCriterion Criterion to use for adding residues to boxes. (1) uses C alpha
952      * only (N1/9 for nucleic acids) (2) uses any atom. (3) uses any rotamer
953      */
954     @Option(names = {"--bC",
955         "--boxInclusionCriterion"}, paramLabel = "1", defaultValue = "1", description = "Criterion to use for adding a residue to a box: (1) uses C alpha only (N1/9 for nucleic acids), (2) uses any atom, and (3) uses any rotamer")
956     private int boxInclusionCriterion;
957 
958     /**
959      * --iB or --initialBox Initial box to optimize.
960      */
961     @Option(names = {"--iB", "--initialBox"}, defaultValue = "0", description = "Initial box to optimize.")
962     private int initialBox;
963 
964     /**
965      * --bf or --boxFinal Final box to optimize.
966      */
967     @Option(names = {"--fB", "--finalBox"}, defaultValue = "2147483647", // Integer.MAX_VALUE
968         description = "Final box to optimize.")
969     private int finalBox;
970 
971   }
972 
973   /**
974    * Collection of ManyBody Window Optimization Options.
975    */
976   private static class WindowOptionGroup {
977 
978     /** --window Size of the sliding window with respect to adjacent residues (default = 7). */
979     @Option(names = {
980         "--window"}, paramLabel = "7", defaultValue = "7", description = "Size of the sliding window with respect to adjacent residues.")
981     private int window;
982 
983     /** --increment Sliding window increment (default = 3). */
984     @Option(names = {
985         "--increment"}, paramLabel = "3", defaultValue = "3", description = "Sliding window increment.")
986     private int increment;
987 
988   }
989 
990   /**
991    * Collection of ManyBody Energy Optimization Options.
992    */
993   private static class EnergyOptionGroup {
994 
995     /** --radius The sliding window and box cutoff radius (Angstroms). */
996     @Option(names = {
997         "--radius"}, paramLabel = "2.0", defaultValue = "2.0", description = "The sliding box and window cutoff radius (Angstroms).")
998     private double cutoff;
999 
1000     /** --tC or --twoBodyCutoff Cutoff distance for two-body interactions. */
1001     @Option(names = {"--tC",
1002         "--twoBodyCutoff"}, paramLabel = "3.0", defaultValue = "3.0", description = "Cutoff distance for two body interactions.")
1003     private double twoBodyCutoff;
1004 
1005     /** -T or --threeBody Include 3-Body interactions in the elimination criteria. */
1006     @Option(names = {"-T",
1007         "--threeBody"}, defaultValue = "false", description = "Include 3-Body interactions in the elimination criteria.")
1008     private boolean threeBody;
1009 
1010     /** --thC or --threeBodyCutoff Cutoff distance for three-body interactions. */
1011     @Option(names = {"--thC",
1012         "--threeBodyCutoff"}, paramLabel = "3.0", defaultValue = "3.0", description = "Cutoff distance for three-body interactions.")
1013     private double threeBodyCutoff;
1014 
1015     /** --pr or --prune Prune no clashes (0), only single clashes (1), or all clashes (2). */
1016     @Option(names = {"--pr",
1017         "--prune"}, paramLabel = "1", defaultValue = "1", description = "Prune no clashes (0), only single clashes (1), or all clashes (2)")
1018     private int prune;
1019 
1020     /**
1021      * --clashThreshold The threshold for pruning clashes. If two self-energies on the same residue
1022      * have an energy difference greater than 25 kcal/mol, the high energy rotamers get pruned.
1023      */
1024     @Option(names = {
1025         "--clashThreshold"}, paramLabel = "25.0", defaultValue = "25.0", description = "The threshold for pruning clashes.")
1026     private double clashThreshold;
1027 
1028     /**
1029      * --pairClashThreshold The threshold for pruning clashes. If two pair-energies on the same
1030      * residues have an energy difference greater than 25 kcal/mol, the high energy rotamers get
1031      * pruned.
1032      */
1033     @Option(names = {
1034         "--pairClashThreshold"}, paramLabel = "25.0", defaultValue = "25.0", description = "The threshold for pruning pair clashes.")
1035     private double pairClashThreshold;
1036 
1037     /** --radius The sliding window and box cutoff radius (Angstroms). */
1038     @Option(names = {
1039             "--kPH", "--pHRestraint"}, paramLabel = "0.0", defaultValue = "0.0", description = "Only allow titration state to change from" +
1040             "standard state is self energy exceeds the restraint.")
1041     private double pHRestraint = 0;
1042   }
1043 
1044   /**
1045    * Collection of ManyBody Residue Selection Options.
1046    */
1047   private static class ResidueOptionGroup {
1048 
1049     /** --ch or --chain Single character chain ID of the residues to optimize. */
1050     @Option(names = {"--ch",
1051         "--chain"}, paramLabel = "<A>", defaultValue = "-1", description = "Include only specified chain ID (default: all chains).")
1052     private String chain;
1053 
1054     /**
1055      * --sR or --start Starting residue to perform the optimization on.
1056      */
1057     @Option(names = {"--sR", "--start"}, defaultValue = "-2147483648", // Integer.MIN_VALUE
1058         description = "Starting residue to optimize (default: all residues).")
1059     private int start;
1060 
1061     /**
1062      * --fi or --final Final residue to perform the optimization.
1063      */
1064     @Option(names = {"--fR",
1065         "--final"}, paramLabel = "<final>", defaultValue = "2147483647", // Integer.MAX_VALUE
1066         description = "Final residue to optimize (default: all residues).")
1067     private int finish;
1068 
1069     /** --lR or --listResidues Choose a list of individual residues to optimize (eg. A11,A24,B40). */
1070     @Option(names = {"--lR",
1071         "--listResidues"}, paramLabel = "<list>", defaultValue = "none", description = "Select a list of residues to optimize (eg. A11,A24,B40).")
1072     private String listResidues;
1073 
1074     /** --oT or --onlyTitrtaion Rotamer optimize only titratable residues. */
1075     @Option(names = {"--oT",
1076             "--onlyTitration"}, paramLabel = "", defaultValue = "false", description = "Rotamer optimize only titratable residues.")
1077     private boolean onlyTitration;
1078 
1079     /** --oP or --onlyProtons Rotamer optimize only proton movement. */
1080     @Option(names = {"--oP",
1081             "--onlyProtons"}, paramLabel = "", defaultValue = "false", description = "Rotamer optimize only proton movement.")
1082     private boolean onlyProtons;
1083 
1084     /** --iR or --interestedResidue Optimize rotamers within some distance of a specific residue. */
1085     @Option(names = {"--iR",
1086             "--interestedResidue"}, paramLabel = "", defaultValue = "-1", description = "Optimize rotamers within some distance of a specific residue.")
1087     private int interestedResidue = -1;
1088 
1089     /** --iC or --inclusionCutoff Distance which rotamers will be included when using only protons, titratable residues, or interested residue. */
1090     @Option(names = {"--iC",
1091             "--inclusionCutoff"}, paramLabel = "", defaultValue = "-1", description = "Distance which rotamers will be included when using only protons, titratable residues, or interested residue.")
1092     private double inclusionCutoff = -1;
1093 
1094   }
1095 
1096 }