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