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       MolecularAssembly protonatedAssembly = titrationManyBody.getProtonatedAssembly();
198       setActiveAssembly(protonatedAssembly);
199       potentialEnergy = protonatedAssembly.getPotentialEnergy();
200     }
201 
202     if (lambdaTerm) {
203       alchemicalOptions.setFirstSystemAlchemistry(activeAssembly);
204       LambdaInterface lambdaInterface = (LambdaInterface) potentialEnergy;
205       double lambda = alchemicalOptions.getInitialLambda();
206       logger.info(format(" Setting ManyBody softcore lambda to: %5.3f", lambda));
207       lambdaInterface.setLambda(lambda);
208     }
209 
210     RotamerOptimization rotamerOptimization = new RotamerOptimization(activeAssembly,
211         potentialEnergy, algorithmListener);
212     rotamerOptimization.setPHRestraint(manyBodyOptions.getPHRestraint());
213     rotamerOptimization.setpH(titrationPH);
214 
215     manyBodyOptions.initRotamerOptimization(rotamerOptimization, activeAssembly);
216     List<Residue> residueList = rotamerOptimization.getResidues();
217 
218     logger.info("\n Initial Potential Energy:");
219     potentialEnergy.energy(false, true);
220 
221     logger.info("\n Initial Rotamer Torsion Angles:");
222     RotamerLibrary.measureRotamers(residueList, false);
223 
224     // Run the optimization.
225     rotamerOptimization.optimize(manyBodyOptions.getAlgorithm(residueList.size()));
226 
227     boolean isTitrating = false;
228     Set<Atom> excludeAtoms = new HashSet<>();
229     int[] optimalRotamers = rotamerOptimization.getOptimumRotamers();
230     if (manyBodyOptions.getTitration()) {
231       isTitrating = titrationManyBody.excludeExcessAtoms(excludeAtoms, optimalRotamers, residueList);
232     }
233 
234     // Log the final result on rank 0.
235     int rank = Comm.world().rank();
236     if (rank == 0) {
237       logger.info(" Final Minimum Energy");
238       double energy = potentialEnergy.energy(false, true);
239       if (isTitrating) {
240         double phBias = rotamerOptimization.getEnergyExpansion().getTotalRotamerPhBias(residueList,
241             optimalRotamers, titrationPH, manyBodyOptions.getPHRestraint());
242         logger.info(format("\n  Rotamer pH Bias    %16.8f", phBias));
243         logger.info(format("  Potential with Bias%16.8f\n", phBias + energy));
244       }
245 
246       // Prevent residues from being renamed based on the existence of hydrogen
247       // atoms (i.e. hydrogen that are excluded from being written out).
248       properties.setProperty("standardizeAtomNames", "false");
249       File modelFile = saveDirFile(activeAssembly.getFile());
250       PDBFilter pdbFilter = new PDBFilter(modelFile, activeAssembly, activeAssembly.getForceField(),
251           properties);
252       if (manyBodyOptions.getTitration()) {
253         String remark = format("Titration pH: %6.3f", titrationPH);
254         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true, new String[]{remark})) {
255           logger.info(format(" Save failed for %s", activeAssembly));
256         }
257       } else {
258         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true)) {
259           logger.info(format(" Save failed for %s", activeAssembly));
260         }
261       }
262     }
263 
264     if (manyBodyOptions.getTitration()) {
265       System.clearProperty("manybody-titration");
266     }
267 
268     return this;
269   }
270 
271   /**
272    * Get ManyBodyOptions to access script parameters.
273    */
274   public ManyBodyOptions getManyBodyOptions() {
275     return manyBodyOptions;
276   }
277 
278   /**
279    * Returns the potential energy of the active assembly. Used during testing assertions.
280    * @return potentialEnergy Potential energy of the active assembly.
281    */
282   public ForceFieldEnergy getPotential() {
283     return potentialEnergy;
284   }
285 
286   /**
287    * {@inheritDoc}
288    */
289   @Override
290   public List<Potential> getPotentials() {
291     List<Potential> potentials;
292     if (potentialEnergy == null) {
293       potentials = Collections.emptyList();
294     } else {
295       potentials = Collections.singletonList(potentialEnergy);
296     }
297     return potentials;
298   }
299 
300 }