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.commands;
39  
40  import ffx.algorithms.cli.AlgorithmsCommand;
41  import ffx.algorithms.cli.ManyBodyOptions;
42  import ffx.algorithms.optimize.RotamerOptimization;
43  import ffx.algorithms.optimize.TitrationManyBody;
44  import ffx.potential.ForceFieldEnergy;
45  import ffx.potential.MolecularAssembly;
46  import ffx.potential.bonded.Atom;
47  import ffx.potential.bonded.LambdaInterface;
48  import ffx.potential.bonded.Residue;
49  import ffx.potential.bonded.Rotamer;
50  import ffx.potential.bonded.RotamerLibrary;
51  import ffx.potential.cli.AlchemicalOptions;
52  import ffx.potential.parsers.PDBFilter;
53  import ffx.utilities.FFXBinding;
54  import org.apache.commons.configuration2.CompositeConfiguration;
55  import picocli.CommandLine;
56  import picocli.CommandLine.Command;
57  import picocli.CommandLine.Mixin;
58  import picocli.CommandLine.Parameters;
59  
60  import java.io.File;
61  import java.io.FileWriter;
62  import java.util.ArrayList;
63  import java.util.HashSet;
64  import java.util.List;
65  import java.util.Set;
66  
67  import static ffx.potential.bonded.NamingUtils.renameAtomsToPDBStandard;
68  import static java.lang.String.format;
69  
70  /**
71   * The ReductionPartition script performs a discrete optimization using a many-body expansion and elimination expressions.
72   * <br>
73   * Usage:
74   * <br>
75   * ffxc ManyBody [options] &lt;filename&gt;
76   */
77  @Command(description = " Run GenZ function for free energy change.", name = "GenZ")
78  public class GenZ extends AlgorithmsCommand {
79  
80    @Mixin
81    private ManyBodyOptions manyBodyOptions;
82  
83    @Mixin
84    private AlchemicalOptions alchemicalOptions;
85  
86    @CommandLine.Option(names = {"--resC", "--residueChain"}, paramLabel = "A",
87        description = "The chain that is mutating.")
88    private String mutatingChain = "A";
89  
90    @CommandLine.Option(names = {"-n", "--residueName"}, paramLabel = "ALA",
91        description = "Mutant residue.")
92    private String resName;
93  
94    @CommandLine.Option(names = {"--rEE", "--ro-ensembleEnergy"}, paramLabel = "0.0",
95        description = "Keep permutations within ensemble Energy kcal/mol from the GMEC.")
96    private String ensembleEnergy = "0.0";
97  
98    @CommandLine.Option(names = {"--un", "--unfolded"}, paramLabel = "false",
99        description = "Run the unfolded state tripeptide.")
100   private boolean unfolded = false;
101 
102   @CommandLine.Option(names = {"--pKa"}, paramLabel = "false",
103       description = "Calculating protonation populations for pKa shift.")
104   private boolean pKa = false;
105 
106   @CommandLine.Option(names = {"--pB", "--printBoltzmann"}, paramLabel = "false",
107       description = "Save the Boltzmann weights of protonated residue and total Boltzmann weights.")
108   private boolean printBoltzmann = false;
109 
110   @CommandLine.Option(names = {"--pF", "--printFiles"}, paramLabel = "false",
111       description = "Write to an energy restart file and ensemble file.")
112   private boolean printFiles = false;
113 
114   @CommandLine.Option(names = {"--rCS", "--recomputeSelf"}, paramLabel = "false",
115       description = "Recompute the self energies after loading a restart file.")
116   private boolean recomputeSelf = false;
117 
118   /**
119    * An XYZ or PDB input file.
120    */
121   @Parameters(arity = "1", paramLabel = "file",
122       description = "XYZ or PDB input file.")
123   private List<String> filenames = null;
124 
125 
126   ForceFieldEnergy potentialEnergy;
127   TitrationManyBody titrationManyBody;
128   /**
129    * Assembly with mutations after running mutate pdb
130    */
131   MolecularAssembly mutatedAssembly;
132   /**
133    * Binding to run mutate pdb
134    */
135   FFXBinding mutatorBinding;
136   /**
137    * All the residues
138    */
139   List<Residue> residues;
140   /**
141    * Residues included in the partition function
142    */
143   List<Residue> selectedResidues;
144   /**
145    * File to save the unfolded state of a protein for free energy prediction
146    */
147   private String unfoldedFileName;
148   /**
149    * Population array for residue rotamers
150    */
151   private double[][] populationArray;
152 
153   /**
154    * ManyBody Constructor.
155    */
156   public GenZ() {
157     super();
158   }
159 
160   /**
161    * ManyBody Constructor.
162    *
163    * @param binding The Binding to use.
164    */
165   public GenZ(FFXBinding binding) {
166     super(binding);
167   }
168 
169   /**
170    * GenZ constructor that sets the command line arguments.
171    *
172    * @param args Command line arguments.
173    */
174   public GenZ(String[] args) {
175     super(args);
176   }
177 
178   /**
179    * {@inheritDoc}
180    */
181   @Override
182   public GenZ run() {
183     if (!init()) {
184       return this;
185     }
186 
187     // Get all the important flags from the manybody options
188     double titrationPH = manyBodyOptions.getTitrationPH();
189     double inclusionCutoff = manyBodyOptions.getInclusionCutoff();
190     int mutatingResidue = manyBodyOptions.getInterestedResidue();
191     boolean onlyTitration = manyBodyOptions.getOnlyTitration();
192     double pHRestraint = manyBodyOptions.getPHRestraint();
193     // Set system property to propagate titration
194     if (manyBodyOptions.getTitration()) {
195       System.setProperty("manybody-titration", "true");
196     }
197 
198     // If soft coring
199     boolean lambdaTerm = alchemicalOptions.hasSoftcore();
200     if (lambdaTerm) {
201       // Turn on softcore van der Waals
202       System.setProperty("lambdaterm", "true");
203       // Turn of alchemical electrostatics
204       System.setProperty("elec-lambdaterm", "false");
205       // Turn on intra-molecular softcore
206       System.setProperty("intramolecular-softcore", "true");
207     }
208     // Set the energy cutoff for permutations to include in the ensemble
209     System.setProperty("ro-ensembleEnergy", ensembleEnergy);
210     activeAssembly = getActiveAssembly(filenames.get(0));
211 
212     //Make an unfolded state assembly when predicting folding free energy difference
213     if (unfolded) {
214       unfoldedFileName = "wt" + mutatingResidue + ".pdb";
215       List<Atom> atoms = activeAssembly.getAtomList();
216       Set<Atom> excludeAtoms = new HashSet<>();
217       for (Atom atom : atoms) {
218         if (atom.getResidueNumber() < mutatingResidue - 1 || atom.getResidueNumber() > mutatingResidue + 1) {
219           excludeAtoms.add(atom);
220         } else if (atom.getResidueNumber() == mutatingResidue - 1 && "H".equals(atom.getName())) {
221           excludeAtoms.add(atom);
222         }
223       }
224       File file = new File(unfoldedFileName);
225       PDBFilter pdbFilter = new PDBFilter(file, activeAssembly, activeAssembly.getForceField(),
226           activeAssembly.getProperties());
227       pdbFilter.writeFile(file, false, excludeAtoms, true, true);
228       setActiveAssembly(getActiveAssembly(unfoldedFileName));
229     }
230 
231     //Allocate arrays for different values coming out of the partition function
232     double[] boltzmannWeights = new double[2];
233     double[] offsets = new double[2];
234     double[][] populationArray = new double[1][55];
235     double[][] titrateBoltzmann = null;
236     double[] protonationBoltzmannSums = null;
237     double totalBoltzmann = 0;
238     List<Residue> residueList = activeAssembly.getResidueList();
239 
240     String mutatedFileName = "";
241     //Call the MutatePDB script and mutate the residue of interest
242     if (filenames.size() == 1 && mutatingResidue != -1) {
243       mutatorBinding = new FFXBinding();
244       if (unfolded) {
245         // mutatorBinding = new Binding('-r', mutatingResidue.toString(), '-n', resName, unfoldedFileName)
246         String[] args = {"-r", String.valueOf(mutatingResidue), "-n", resName, unfoldedFileName};
247         mutatorBinding.setVariable("args", args);
248       } else {
249         // mutatorBinding = new Binding('-r', mutatingResidue.toString(), '-n', resName, filenames.get(0), '--ch', mutatingChain)
250         String[] args = {"-r", String.valueOf(mutatingResidue), "-n", resName, "--ch", mutatingChain, unfoldedFileName};
251         mutatorBinding.setVariable("args", args);
252       }
253       MutatePDB mutatePDB = new MutatePDB(mutatorBinding);
254       mutatePDB.run();
255       mutatedFileName = (String) mutatorBinding.getVariable("versionFileName");
256     }
257 
258     String listResidues = "";
259     // Select residues with alpha carbons within the inclusion cutoff or
260     // Select only the titrating residues or the titrating residues and those within the inclusion cutoff
261     if ((mutatingResidue != -1 && inclusionCutoff != -1) || onlyTitration) {
262       listResidues = manyBodyOptions.selectInclusionResidues(residueList, mutatingResidue, onlyTitration, inclusionCutoff);
263     }
264 
265     String filename = filenames.get(0);
266 
267     //Set the number of assemblies the partition function will be calculated for
268     int numLoop = 1;
269     if (mutatingResidue != -1) {
270       numLoop = 2;
271     }
272 
273     //Prepare variables for saving out the highest population rotamers (optimal rotamers)
274     int[] optimalRotamers;
275     Set<Atom> excludeAtoms = new HashSet<>();
276 
277     //Calculate all possible permutations for the number of assembles
278     for (int j = 0; j < numLoop; j++) {
279 
280       // Load the MolecularAssembly second molecular assembly if applicable.
281       if (j > 0) {
282         if (filenames.size() == 1) {
283           mutatedAssembly = getActiveAssembly(mutatedFileName);
284           setActiveAssembly(mutatedAssembly);
285           logger.info(activeAssembly.getResidueList().toString());
286           activeAssembly.getPotentialEnergy().energy();
287           filename = mutatedFileName;
288         } else {
289           setActiveAssembly(getActiveAssembly(filenames.get(j)));
290           filename = filenames.get(j);
291         }
292       }
293 
294       if (activeAssembly == null) {
295         logger.info(helpString());
296         return this;
297       }
298 
299       CompositeConfiguration properties = activeAssembly.getProperties();
300 
301       // Application of rotamers uses side-chain atom naming from the PDB.
302       if (properties.getBoolean("standardizeAtomNames", false)) {
303         renameAtomsToPDBStandard(activeAssembly);
304       }
305 
306       // Update the potential energy to match current assembly
307       activeAssembly.getPotentialEnergy().setPrintOnFailure(false, false);
308       potentialEnergy = activeAssembly.getPotentialEnergy();
309 
310       // Selecting residues
311       if (!pKa || onlyTitration) {
312         manyBodyOptions.setListResidues(listResidues);
313       }
314 
315       // Collect residues to optimize.
316       residues = manyBodyOptions.collectResidues(activeAssembly);
317       if (residues == null || residues.isEmpty()) {
318         logger.info(" There are no residues in the active system to optimize.");
319         return this;
320       }
321 
322       // Handle rotamer optimization with titration.
323       if (manyBodyOptions.getTitration()) {
324         logger.info("\n Adding titration hydrogen to : " + filenames.get(0) + "\n");
325 
326         // Collect residue numbers.
327         List<Integer> resNumberList = new ArrayList<>();
328         for (Residue residue : residues) {
329           resNumberList.add(residue.getResidueNumber());
330         }
331 
332         // Create new MolecularAssembly with additional protons and update the ForceFieldEnergy
333         titrationManyBody = new TitrationManyBody(filename, activeAssembly.getForceField(),
334             resNumberList, titrationPH, manyBodyOptions);
335         MolecularAssembly protonatedAssembly = titrationManyBody.getProtonatedAssembly();
336         setActiveAssembly(protonatedAssembly);
337         potentialEnergy = protonatedAssembly.getPotentialEnergy();
338       }
339 
340       // Turn on softcoring lambda
341       if (lambdaTerm) {
342         alchemicalOptions.setFirstSystemAlchemistry(activeAssembly);
343         LambdaInterface lambdaInterface = (LambdaInterface) potentialEnergy;
344         double lambda = alchemicalOptions.getInitialLambda();
345         logger.info(format(" Setting ManyBody softcore lambda to: %5.3f", lambda));
346         lambdaInterface.setLambda(lambda);
347       }
348 
349       //Run rotamer optimization with specified parameters
350       RotamerOptimization rotamerOptimization = new RotamerOptimization(activeAssembly,
351           potentialEnergy, algorithmListener);
352       rotamerOptimization.setPrintFiles(printFiles);
353       rotamerOptimization.setWriteEnergyRestart(printFiles);
354       rotamerOptimization.setPHRestraint(pHRestraint);
355       rotamerOptimization.setRecomputeSelf(recomputeSelf);
356       rotamerOptimization.setpH(titrationPH);
357 
358       manyBodyOptions.initRotamerOptimization(rotamerOptimization, activeAssembly);
359 
360       // Initialize fractions for selected residues
361       selectedResidues = rotamerOptimization.getResidues();
362       rotamerOptimization.initFraction(selectedResidues);
363 
364       logger.info("\n Initial Potential Energy:");
365       potentialEnergy.energy(false, true);
366 
367       logger.info("\n Initial Rotamer Torsion Angles:");
368       RotamerLibrary.measureRotamers(selectedResidues, false);
369 
370       // Run the optimization.
371       rotamerOptimization.optimize(manyBodyOptions.getAlgorithm(selectedResidues.size()));
372 
373       int[] currentRotamers = new int[selectedResidues.size()];
374 
375       //Calculate possible permutations for assembly
376       try {
377         rotamerOptimization.getFractions(selectedResidues.toArray(new Residue[0]), 0, currentRotamers);
378       } catch (Exception e) {
379         logger.severe(" Error calculating fractions: " + e.getMessage());
380         return this;
381       }
382 
383       //Collect the Bolztmann weights and calculated offset of each assembly
384       boltzmannWeights[j] = rotamerOptimization.getTotalBoltzmann();
385       offsets[j] = rotamerOptimization.getRefEnergy();
386 
387       //Calculate the populations for the residue rotamers
388       populationArray = rotamerOptimization.getFraction();
389       if (printBoltzmann) {
390         titrateBoltzmann = rotamerOptimization.getPopulationBoltzmann();
391         totalBoltzmann = rotamerOptimization.getTotalBoltzmann();
392       }
393 
394       // Collect the most populous rotamers
395       optimalRotamers = rotamerOptimization.getOptimumRotamers();
396       if (manyBodyOptions.getTitration()) {
397         // Remove excess atoms from titratable residues
398         titrationManyBody.excludeExcessAtoms(excludeAtoms, optimalRotamers, selectedResidues);
399       }
400 
401       // Calculate the protonation populations
402       if (pKa) {
403         rotamerOptimization.getProtonationPopulations(selectedResidues.toArray(new Residue[0]));
404       }
405 
406 
407     }
408 
409     //Print information from the fraction protonated calculations
410     try (FileWriter fileWriter = new FileWriter("populations.txt")) {
411       int residueIndex = 0;
412       for (Residue residue : selectedResidues) {
413         fileWriter.write("\n");
414         protonationBoltzmannSums = new double[selectedResidues.size()];
415         // Set sums for to protonated, deprotonated, and tautomer states of titratable residues
416         Rotamer[] rotamers = residue.getRotamers();
417         for (int rotIndex = 0; rotIndex < rotamers.length; rotIndex++) {
418           String rotPop = format("%.6f", populationArray[residueIndex][rotIndex]);
419           fileWriter.write(residue.getName() + residue.getResidueNumber() + "\t" +
420               rotamers[rotIndex].toString() + "\t" + rotPop + "\n");
421           if (pKa) {
422             switch (rotamers[rotIndex].getName()) {
423               case "HIS":
424               case "LYS":
425               case "GLH":
426               case "ASH":
427               case "CYS":
428                 if (printBoltzmann) {
429                   protonationBoltzmannSums[residueIndex] += titrateBoltzmann[residueIndex][rotIndex];
430                 }
431                 break;
432               default:
433                 break;
434             }
435           }
436 
437         }
438         // Print protonated and total boltzmann values
439         if (printBoltzmann) {
440           logger.info("\n Residue " + residue.getName() + residue.getResidueNumber() + " Protonated Boltzmann: " +
441               protonationBoltzmannSums[residueIndex]);
442           logger.info("\n Total Boltzmann: " + totalBoltzmann);
443         }
444         residueIndex += 1;
445       }
446       System.out.println("\n Successfully wrote to the populations file.");
447     } catch (Exception e) {
448       logger.severe("Error writing populations file: " + e.getMessage());
449     }
450 
451 
452     // Save the pdb file with the most popular rotamers for all residues included in the partition function
453     System.setProperty("standardizeAtomNames", "false");
454     File modelFile = saveDirFile(activeAssembly.getFile());
455     PDBFilter pdbFilter = new PDBFilter(modelFile, activeAssembly, activeAssembly.getForceField(),
456         activeAssembly.getProperties());
457     if (manyBodyOptions.getTitration()) {
458       String remark = format("Titration pH: %6.3f", titrationPH);
459       if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true, new String[]{remark})) {
460         logger.info(format(" Save failed for %s", activeAssembly));
461       }
462     } else {
463       if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true)) {
464         logger.info(format(" Save failed for %s", activeAssembly));
465       }
466     }
467     if (mutatingResidue != -1) {
468       //Calculate Gibbs free energy change of mutating residues
469       double gibbs = -(0.6) * (Math.log(boltzmannWeights[1] / boltzmannWeights[0]));
470       logger.info("\n Gibbs Free Energy Change: " + gibbs);
471     }
472 
473 
474     return this;
475   }
476 
477 
478   /**
479    * Returns the potential energy of the active assembly. Used during testing assertions.
480    *
481    * @return potentialEnergy Potential energy of the active assembly.
482    */
483   public ForceFieldEnergy getPotential() {
484     return potentialEnergy;
485   }
486 
487   public double[][] getPopulationArray() {
488     return populationArray;
489   }
490 }