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-2026.
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     // Atomic clashes are expected and will be handled using direct induced dipoles.
169     System.setProperty("sor-scf-fallback", "false");
170     System.setProperty("direct-scf-fallback", "true");
171 
172     // Get all the important flags from the manybody options
173     double titrationPH = manyBodyOptions.getTitrationPH();
174     double inclusionCutoff = manyBodyOptions.getInclusionCutoff();
175     int mutatingResidue = manyBodyOptions.getInterestedResidue();
176     boolean onlyTitration = manyBodyOptions.getOnlyTitration();
177     double pHRestraint = manyBodyOptions.getPHRestraint();
178     // Set system property to propagate titration
179     if (manyBodyOptions.getTitration()) {
180       System.setProperty("manybody-titration", "true");
181     }
182 
183     // If soft coring
184     boolean lambdaTerm = alchemicalOptions.hasSoftcore();
185     if (lambdaTerm) {
186       // Turn on softcore van der Waals
187       System.setProperty("lambdaterm", "true");
188       // Turn of alchemical electrostatics
189       System.setProperty("elec-lambdaterm", "false");
190       // Turn on intra-molecular softcore
191       System.setProperty("intramolecular-softcore", "true");
192     }
193     // Set the energy cutoff for permutations to include in the ensemble
194     System.setProperty("ro-ensembleEnergy", ensembleEnergy);
195 
196     // Load the MolecularAssembly.
197     activeAssembly = getActiveAssembly(filename);
198     if (activeAssembly == null) {
199       logger.info(helpString());
200       return this;
201     }
202 
203     // Set the filename.
204     filename = activeAssembly.getFile().getAbsolutePath();
205 
206     // Make an unfolded state assembly when predicting folding free energy difference
207     String unfoldedFileName = null;
208     if (unfolded) {
209       // File to save the unfolded state of a protein for free energy prediction
210       unfoldedFileName = "wt" + mutatingResidue + ".pdb";
211       List<Atom> atoms = activeAssembly.getAtomList();
212       Set<Atom> excludeAtoms = new HashSet<>();
213       for (Atom atom : atoms) {
214         if (atom.getResidueNumber() < mutatingResidue - 1 || atom.getResidueNumber() > mutatingResidue + 1) {
215           excludeAtoms.add(atom);
216         } else if (atom.getResidueNumber() == mutatingResidue - 1 && "H".equals(atom.getName())) {
217           excludeAtoms.add(atom);
218         }
219       }
220       File file = new File(unfoldedFileName);
221       PDBFilter pdbFilter = new PDBFilter(file, activeAssembly, activeAssembly.getForceField(),
222           activeAssembly.getProperties());
223       pdbFilter.writeFile(file, false, excludeAtoms, true, true);
224 
225       // Load the unfolded state system.
226       activeAssembly = getActiveAssembly(unfoldedFileName);
227       filename = activeAssembly.getFile().getAbsolutePath();
228     }
229 
230     // Allocate arrays for different values coming out of the partition function
231     double[] boltzmannWeights = new double[2];
232     double[][] titrateBoltzmann = null;
233     double totalBoltzmann = 0;
234     List<Residue> residueList = activeAssembly.getResidueList();
235 
236     String mutatedFileName = "";
237     // Call the MutatePDB script and mutate the residue of interest
238     if (mutatingResidue != -1) {
239       FFXBinding mutatorBinding = new FFXBinding();
240       if (unfolded) {
241         String[] args = {"-r", String.valueOf(mutatingResidue), "-n", resName, unfoldedFileName};
242         mutatorBinding.setVariable("args", args);
243       } else {
244         String[] args = {"-r", String.valueOf(mutatingResidue), "-n", resName, "--ch", mutatingChain, filename};
245         mutatorBinding.setVariable("args", args);
246       }
247       MutatePDB mutatePDB = new MutatePDB(mutatorBinding);
248       mutatePDB.run();
249       mutatedFileName = (String) mutatorBinding.getVariable("versionFileName");
250     }
251 
252     String listResidues = "";
253     // Select residues with alpha carbons within the inclusion cutoff or
254     // Select only the titrating residues or the titrating residues and those within the inclusion cutoff
255     if ((mutatingResidue != -1 && inclusionCutoff != -1) || onlyTitration) {
256       listResidues = manyBodyOptions.selectInclusionResidues(residueList, mutatingResidue, onlyTitration, inclusionCutoff);
257     }
258 
259     // Set the number of assemblies the partition function will be calculated for
260     int numLoop = 1;
261     if (mutatingResidue != -1) {
262       numLoop = 2;
263     }
264 
265     //Prepare variables for saving out the highest population rotamers (optimal rotamers)
266     int[] optimalRotamers;
267     Set<Atom> excludeAtoms = new HashSet<>();
268 
269     // Calculate all possible permutations for the number of assembles
270     for (int j = 0; j < numLoop; j++) {
271 
272       // Load the MolecularAssembly second molecular assembly if applicable.
273       if (j > 0) {
274         activeAssembly = getActiveAssembly(mutatedFileName);
275         activeAssembly.getPotentialEnergy().energy();
276         filename = activeAssembly.getFile().getAbsolutePath();
277       }
278 
279       if (activeAssembly == null) {
280         logger.info(helpString());
281         return this;
282       }
283 
284       CompositeConfiguration properties = activeAssembly.getProperties();
285 
286       // Application of rotamers uses side-chain atom naming from the PDB.
287       if (properties.getBoolean("standardizeAtomNames", false)) {
288         renameAtomsToPDBStandard(activeAssembly);
289       }
290 
291       // Update the potential energy to match current assembly
292       activeAssembly.getPotentialEnergy().setPrintOnFailure(false, false);
293       potentialEnergy = activeAssembly.getPotentialEnergy();
294 
295       // Selecting residues
296       if (!pKa || onlyTitration) {
297         manyBodyOptions.setListResidues(listResidues);
298       }
299 
300       // Collect residues to optimize.
301       List<Residue> residues = manyBodyOptions.collectResidues(activeAssembly);
302       if (residues == null || residues.isEmpty()) {
303         logger.info(" There are no residues in the active system to optimize.");
304         return this;
305       }
306 
307       // Handle rotamer optimization with titration.
308       TitrationManyBody titrationManyBody = null;
309       if (manyBodyOptions.getTitration()) {
310         logger.info("\n Adding titration hydrogen to : " + filename + "\n");
311 
312         // Collect residue numbers.
313         List<Integer> resNumberList = new ArrayList<>();
314         for (Residue residue : residues) {
315           resNumberList.add(residue.getResidueNumber());
316         }
317 
318         // Create new MolecularAssembly with additional protons and update the ForceFieldEnergy
319         titrationManyBody = new TitrationManyBody(filename, activeAssembly.getForceField(),
320             resNumberList, titrationPH, manyBodyOptions);
321         activeAssembly = titrationManyBody.getProtonatedAssembly();
322         potentialEnergy = activeAssembly.getPotentialEnergy();
323       }
324 
325       // Turn on softcoring lambda
326       if (lambdaTerm) {
327         alchemicalOptions.setFirstSystemAlchemistry(activeAssembly);
328         LambdaInterface lambdaInterface = potentialEnergy;
329         double lambda = alchemicalOptions.getInitialLambda();
330         logger.info(format(" Setting ManyBody softcore lambda to: %5.3f", lambda));
331         lambdaInterface.setLambda(lambda);
332       }
333 
334       //Run rotamer optimization with specified parameters
335       RotamerOptimization rotamerOptimization = new RotamerOptimization(activeAssembly, potentialEnergy, algorithmListener);
336       rotamerOptimization.setPrintFiles(printFiles);
337       rotamerOptimization.setWriteEnergyRestart(printFiles);
338       rotamerOptimization.setPHRestraint(pHRestraint);
339       rotamerOptimization.setRecomputeSelf(recomputeSelf);
340       rotamerOptimization.setpH(titrationPH);
341 
342       manyBodyOptions.initRotamerOptimization(rotamerOptimization, activeAssembly);
343 
344       // Initialize fractions for selected residues
345       List<Residue> selectedResidues = rotamerOptimization.getResidues();
346       rotamerOptimization.initFraction(selectedResidues);
347 
348       logger.info("\n Initial Potential Energy:");
349       potentialEnergy.energy(false, true);
350 
351       logger.info("\n Initial Rotamer Torsion Angles:");
352       RotamerLibrary.measureRotamers(selectedResidues, false);
353 
354       // Run the optimization.
355       rotamerOptimization.optimize(manyBodyOptions.getAlgorithm(selectedResidues.size()));
356 
357       int[] currentRotamers = new int[selectedResidues.size()];
358 
359       // Calculate possible permutations for assembly
360       try {
361         rotamerOptimization.getFractions(selectedResidues.toArray(new Residue[0]), 0, currentRotamers);
362       } catch (Exception e) {
363         logger.severe(" Error calculating fractions: " + e.getMessage());
364         return this;
365       }
366 
367       // Collect the Boltzmann weights and calculated offset of each assembly
368       boltzmannWeights[j] = rotamerOptimization.getTotalBoltzmann();
369 
370       // Calculate the populations for the residue rotamers
371       populationArray = rotamerOptimization.getFraction();
372       if (printBoltzmann) {
373         titrateBoltzmann = rotamerOptimization.getPopulationBoltzmann();
374         totalBoltzmann = rotamerOptimization.getTotalBoltzmann();
375       }
376 
377       // Collect the most populous rotamers
378       optimalRotamers = rotamerOptimization.getOptimumRotamers();
379       if (manyBodyOptions.getTitration()) {
380         // Remove excess atoms from titratable residues
381         titrationManyBody.excludeExcessAtoms(excludeAtoms, optimalRotamers, selectedResidues);
382       }
383 
384       // Calculate the protonation populations
385       if (pKa) {
386         rotamerOptimization.getProtonationPopulations(selectedResidues.toArray(new Residue[0]));
387       }
388 
389       // Print information from the fraction protonated calculations
390       String populationFilename = "populations.txt";
391       if (j > 0) {
392         populationFilename = "populations."  + j + ".txt";
393       }
394       try (FileWriter fileWriter = new FileWriter(populationFilename)) {
395         int residueIndex = 0;
396         for (Residue residue : selectedResidues) {
397           fileWriter.write("\n");
398           double protonationBoltzmannSum = 0.0;
399           // Set sums for to protonated, deprotonated, and tautomer states of titratable residues
400           Rotamer[] rotamers = residue.getRotamers();
401           for (int rotIndex = 0; rotIndex < rotamers.length; rotIndex++) {
402             String rotPop = format("%.6f", populationArray[residueIndex][rotIndex]);
403             fileWriter.write(residue.getName() + residue.getResidueNumber() + "\t" +
404                 rotamers[rotIndex].toString() + "\t" + rotPop + "\n");
405             if (pKa) {
406               switch (rotamers[rotIndex].getName()) {
407                 case "HIS":
408                 case "LYS":
409                 case "GLH":
410                 case "ASH":
411                 case "CYS":
412                   if (printBoltzmann) {
413                     protonationBoltzmannSum += titrateBoltzmann[residueIndex][rotIndex];
414                   }
415                   break;
416                 default:
417                   break;
418               }
419             }
420 
421           }
422           // Print protonated and total boltzmann values
423           if (printBoltzmann) {
424             logger.info("\n Residue " + residue.getName() + residue.getResidueNumber()
425                 + " Protonated Boltzmann: " + protonationBoltzmannSum);
426           }
427           residueIndex += 1;
428         }
429         logger.info("\n Total Boltzmann: " + totalBoltzmann);
430         logger.info("\n Successfully wrote to the populations file: " + populationFilename);
431       } catch (Exception e) {
432         logger.severe("Error writing populations file: " + e.getMessage());
433       }
434     }
435 
436     // Save the pdb file with the most popular rotamers for all residues included in the partition function
437     System.setProperty("standardizeAtomNames", "false");
438     File modelFile = saveDirFile(activeAssembly.getFile());
439     PDBFilter pdbFilter = new PDBFilter(modelFile, activeAssembly, activeAssembly.getForceField(),
440         activeAssembly.getProperties());
441     if (manyBodyOptions.getTitration()) {
442       String remark = format("Titration pH: %6.3f", titrationPH);
443       if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true, new String[]{remark})) {
444         logger.info(format(" Save failed for %s", activeAssembly));
445       }
446     } else {
447       if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true)) {
448         logger.info(format(" Save failed for %s", activeAssembly));
449       }
450     }
451     if (mutatingResidue != -1) {
452       CompositeConfiguration properties = activeAssembly.getProperties();
453       String temp = properties.getString("temperature", "298.15");
454       double temperature = 298.15;
455       if (temp != null) {
456         temperature = parseDouble(temp);
457       }
458       double kT = temperature * Constants.R;
459       // Calculate free energy difference for mutating a residue.
460       double dG = -kT * log(boltzmannWeights[1] / boltzmannWeights[0]);
461       logger.info(format("\n Mutation Free Energy Difference: %12.8f (kcal/mol)", dG));
462     }
463 
464     return this;
465   }
466 
467   /**
468    * The population for each rotamer of each residue.
469    * @return The population array.
470    */
471   public double[][] getPopulationArray() {
472     return populationArray;
473   }
474 
475   /**
476    * Returns the potential energy of the active assembly. Used during testing assertions.
477    *
478    * @return potentialEnergy Potential energy of the active assembly.
479    */
480   public ForceFieldEnergy getPotential() {
481     return potentialEnergy;
482   }
483 
484 }