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 edu.rit.pj.Comm;
41  import ffx.algorithms.cli.AlgorithmsCommand;
42  import ffx.algorithms.cli.ManyBodyOptions;
43  import ffx.algorithms.optimize.RotamerOptimization;
44  import ffx.algorithms.optimize.TitrationManyBody;
45  import ffx.numerics.Potential;
46  import ffx.potential.ForceFieldEnergy;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.LambdaInterface;
50  import ffx.potential.bonded.Residue;
51  import ffx.potential.bonded.RotamerLibrary;
52  import ffx.potential.cli.AlchemicalOptions;
53  import ffx.potential.parsers.PDBFilter;
54  import ffx.utilities.FFXBinding;
55  import org.apache.commons.configuration2.CompositeConfiguration;
56  import picocli.CommandLine.Command;
57  import picocli.CommandLine.Mixin;
58  import picocli.CommandLine.Parameters;
59  
60  import java.io.File;
61  import java.util.ArrayList;
62  import java.util.Collections;
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 ManyBody 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 ManyBody algorithm on a system.", name = "ManyBody")
78  public class ManyBody extends AlgorithmsCommand {
79  
80    @Mixin
81    private ManyBodyOptions manyBodyOptions;
82  
83    @Mixin
84    private AlchemicalOptions alchemicalOptions;
85  
86    /**
87     * An XYZ or PDB input file.
88     */
89    @Parameters(arity = "1", paramLabel = "file",
90        description = "XYZ or PDB input file.")
91    private String filename;
92  
93    private ForceFieldEnergy potentialEnergy;
94    private TitrationManyBody titrationManyBody;
95  
96    /**
97     * ManyBody Constructor.
98     */
99    public ManyBody() {
100     super();
101   }
102 
103   /**
104    * ManyBody Constructor.
105    * @param binding The Binding to use.
106    */
107   public ManyBody(FFXBinding binding) {
108     super(binding);
109   }
110 
111   /**
112    * ManyBody constructor that sets the command line arguments.
113    * @param args Command line arguments.
114    */
115   public ManyBody(String[] args) {
116     super(args);
117   }
118 
119   /**
120    * {@inheritDoc}
121    */
122   @Override
123   public ManyBody run() {
124 
125     if (!init()) {
126       return this;
127     }
128 
129     // This flag is for ForceFieldEnergyOpenMM and must be set before reading files.
130     // It enforces that all torsions include a Fourier series with 6 terms.
131     // Otherwise, during titration the number of terms for each torsion can change and
132     // cause updateParametersInContext to throw an exception.
133     double titrationPH = manyBodyOptions.getTitrationPH();
134 
135     if (manyBodyOptions.getTitration()) {
136       System.setProperty("manybody-titration", "true");
137     }
138 
139     // Turn on computation of lambda derivatives if softcore atoms exist.
140     boolean lambdaTerm = alchemicalOptions.hasSoftcore();
141     if (lambdaTerm) {
142       // Turn on softcore van der Waals
143       System.setProperty("lambdaterm", "true");
144       // Turn off alchemical electrostatics
145       System.setProperty("elec-lambdaterm", "false");
146       // Turn on intra-molecular softcore
147       System.setProperty("intramolecular-softcore", "true");
148     }
149 
150     // Load the MolecularAssembly.
151     activeAssembly = getActiveAssembly(filename);
152 
153 
154     if (activeAssembly == null) {
155       logger.info(helpString());
156       return this;
157     }
158 
159     String listResidues = "";
160     if (manyBodyOptions.getOnlyTitration() ||
161         manyBodyOptions.getInterestedResidue() != -1 && manyBodyOptions.getInclusionCutoff() != -1) {
162       listResidues = manyBodyOptions.selectInclusionResidues(activeAssembly.getResidueList(),
163           manyBodyOptions.getInterestedResidue(), manyBodyOptions.getOnlyTitration(), manyBodyOptions.getInclusionCutoff());
164       manyBodyOptions.setListResidues(listResidues);
165     }
166 
167     CompositeConfiguration properties = activeAssembly.getProperties();
168 
169     // Application of rotamers uses side-chain atom naming from the PDB.
170     if (properties.getBoolean("standardizeAtomNames", false)) {
171       renameAtomsToPDBStandard(activeAssembly);
172     }
173 
174     activeAssembly.getPotentialEnergy().setPrintOnFailure(false, false);
175     potentialEnergy = activeAssembly.getPotentialEnergy();
176 
177     // Collect residues to optimize.
178     List<Residue> residues = manyBodyOptions.collectResidues(activeAssembly);
179     if (residues == null || residues.isEmpty()) {
180       logger.info(" There are no residues in the active system to optimize.");
181       return this;
182     }
183 
184     // Handle rotamer optimization with titration.
185     if (manyBodyOptions.getTitration()) {
186       logger.info("\n Adding titration hydrogen to : " + filename + "\n");
187 
188       // Collect residue numbers.
189       List<Integer> resNumberList = new ArrayList<>();
190       for (Residue residue : residues) {
191         resNumberList.add(residue.getResidueNumber());
192       }
193 
194       // Create new MolecularAssembly with additional protons and update the ForceFieldEnergy
195       titrationManyBody = new TitrationManyBody(filename, activeAssembly.getForceField(),
196           resNumberList, titrationPH, manyBodyOptions);
197       activeAssembly = titrationManyBody.getProtonatedAssembly();
198       potentialEnergy = activeAssembly.getPotentialEnergy();
199     }
200 
201     if (lambdaTerm) {
202       alchemicalOptions.setFirstSystemAlchemistry(activeAssembly);
203       LambdaInterface lambdaInterface = potentialEnergy;
204       double lambda = alchemicalOptions.getInitialLambda();
205       logger.info(format(" Setting ManyBody softcore lambda to: %5.3f", lambda));
206       lambdaInterface.setLambda(lambda);
207     }
208 
209     RotamerOptimization rotamerOptimization = new RotamerOptimization(activeAssembly,
210         potentialEnergy, algorithmListener);
211     rotamerOptimization.setPHRestraint(manyBodyOptions.getPHRestraint());
212     rotamerOptimization.setpH(titrationPH);
213 
214     manyBodyOptions.initRotamerOptimization(rotamerOptimization, activeAssembly);
215     List<Residue> residueList = rotamerOptimization.getResidues();
216 
217     logger.info("\n Initial Potential Energy:");
218     potentialEnergy.energy(false, true);
219 
220     logger.info("\n Initial Rotamer Torsion Angles:");
221     RotamerLibrary.measureRotamers(residueList, false);
222 
223     // Run the optimization.
224     rotamerOptimization.optimize(manyBodyOptions.getAlgorithm(residueList.size()));
225 
226     boolean isTitrating = false;
227     Set<Atom> excludeAtoms = new HashSet<>();
228     int[] optimalRotamers = rotamerOptimization.getOptimumRotamers();
229     if (manyBodyOptions.getTitration()) {
230       isTitrating = titrationManyBody.excludeExcessAtoms(excludeAtoms, optimalRotamers, residueList);
231     }
232 
233     // Log the final result on rank 0.
234     int rank = Comm.world().rank();
235     if (rank == 0) {
236       logger.info(" Final Minimum Energy");
237       double energy = potentialEnergy.energy(false, true);
238       if (isTitrating) {
239         double phBias = rotamerOptimization.getEnergyExpansion().getTotalRotamerPhBias(residueList,
240             optimalRotamers, titrationPH, manyBodyOptions.getPHRestraint());
241         logger.info(format("\n  Rotamer pH Bias    %16.8f", phBias));
242         logger.info(format("  Potential with Bias%16.8f\n", phBias + energy));
243       }
244 
245       // Prevent residues from being renamed based on the existence of hydrogen
246       // atoms (i.e. hydrogen that are excluded from being written out).
247       properties.setProperty("standardizeAtomNames", "false");
248       File modelFile = saveDirFile(activeAssembly.getFile());
249       PDBFilter pdbFilter = new PDBFilter(modelFile, activeAssembly, activeAssembly.getForceField(),
250           properties);
251       if (manyBodyOptions.getTitration()) {
252         String remark = format("Titration pH: %6.3f", titrationPH);
253         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true, new String[]{remark})) {
254           logger.info(format(" Save failed for %s", activeAssembly));
255         }
256       } else {
257         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true)) {
258           logger.info(format(" Save failed for %s", activeAssembly));
259         }
260       }
261     }
262 
263     if (manyBodyOptions.getTitration()) {
264       System.clearProperty("manybody-titration");
265     }
266 
267     return this;
268   }
269 
270   /**
271    * Get ManyBodyOptions to access script parameters.
272    */
273   public ManyBodyOptions getManyBodyOptions() {
274     return manyBodyOptions;
275   }
276 
277   /**
278    * Returns the potential energy of the active assembly. Used during testing assertions.
279    * @return potentialEnergy Potential energy of the active assembly.
280    */
281   public ForceFieldEnergy getPotential() {
282     return potentialEnergy;
283   }
284 
285   /**
286    * {@inheritDoc}
287    */
288   @Override
289   public List<Potential> getPotentials() {
290     List<Potential> potentials;
291     if (potentialEnergy == null) {
292       potentials = Collections.emptyList();
293     } else {
294       potentials = Collections.singletonList(potentialEnergy);
295     }
296     return potentials;
297   }
298 
299 }