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 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     // Atomic clashes are expected and will be handled using direct induced dipoles.
130     System.setProperty("sor-scf-fallback", "false");
131     System.setProperty("direct-scf-fallback", "true");
132 
133     // This flag is for ForceFieldEnergyOpenMM and must be set before reading files.
134     // It enforces that all torsions include a Fourier series with 6 terms.
135     // Otherwise, during titration the number of terms for each torsion can change and
136     // cause updateParametersInContext to throw an exception.
137     double titrationPH = manyBodyOptions.getTitrationPH();
138 
139     if (manyBodyOptions.getTitration()) {
140       System.setProperty("manybody-titration", "true");
141     }
142 
143     // Turn on computation of lambda derivatives if softcore atoms exist.
144     boolean lambdaTerm = alchemicalOptions.hasSoftcore();
145     if (lambdaTerm) {
146       // Turn on softcore van der Waals
147       System.setProperty("lambdaterm", "true");
148       // Turn off alchemical electrostatics
149       System.setProperty("elec-lambdaterm", "false");
150       // Turn on intra-molecular softcore
151       System.setProperty("intramolecular-softcore", "true");
152     }
153 
154     // Load the MolecularAssembly.
155     activeAssembly = getActiveAssembly(filename);
156 
157     if (activeAssembly == null) {
158       logger.info(helpString());
159       return this;
160     }
161 
162     String listResidues = "";
163     if (manyBodyOptions.getOnlyTitration() ||
164         manyBodyOptions.getInterestedResidue() != -1 && manyBodyOptions.getInclusionCutoff() != -1) {
165       listResidues = manyBodyOptions.selectInclusionResidues(activeAssembly.getResidueList(),
166           manyBodyOptions.getInterestedResidue(), manyBodyOptions.getOnlyTitration(), manyBodyOptions.getInclusionCutoff());
167       manyBodyOptions.setListResidues(listResidues);
168     }
169 
170     CompositeConfiguration properties = activeAssembly.getProperties();
171 
172     // Application of rotamers uses side-chain atom naming from the PDB.
173     if (properties.getBoolean("standardizeAtomNames", false)) {
174       renameAtomsToPDBStandard(activeAssembly);
175     }
176 
177     activeAssembly.getPotentialEnergy().setPrintOnFailure(false, false);
178     potentialEnergy = activeAssembly.getPotentialEnergy();
179 
180     // Collect residues to optimize.
181     List<Residue> residues = manyBodyOptions.collectResidues(activeAssembly);
182     if (residues == null || residues.isEmpty()) {
183       logger.info(" There are no residues in the active system to optimize.");
184       return this;
185     }
186 
187     // Handle rotamer optimization with titration.
188     if (manyBodyOptions.getTitration()) {
189       logger.info("\n Adding titration hydrogen to : " + filename + "\n");
190 
191       // Collect residue numbers.
192       List<Integer> resNumberList = new ArrayList<>();
193       for (Residue residue : residues) {
194         resNumberList.add(residue.getResidueNumber());
195       }
196 
197       // Create new MolecularAssembly with additional protons and update the ForceFieldEnergy
198       titrationManyBody = new TitrationManyBody(filename, activeAssembly.getForceField(),
199           resNumberList, titrationPH, manyBodyOptions);
200       activeAssembly = titrationManyBody.getProtonatedAssembly();
201       potentialEnergy = activeAssembly.getPotentialEnergy();
202     }
203 
204     if (lambdaTerm) {
205       alchemicalOptions.setFirstSystemAlchemistry(activeAssembly);
206       LambdaInterface lambdaInterface = potentialEnergy;
207       double lambda = alchemicalOptions.getInitialLambda();
208       logger.info(format(" Setting ManyBody softcore lambda to: %5.3f", lambda));
209       lambdaInterface.setLambda(lambda);
210     }
211 
212     RotamerOptimization rotamerOptimization = new RotamerOptimization(activeAssembly,
213         potentialEnergy, algorithmListener);
214     rotamerOptimization.setPHRestraint(manyBodyOptions.getPHRestraint());
215     rotamerOptimization.setpH(titrationPH);
216 
217     manyBodyOptions.initRotamerOptimization(rotamerOptimization, activeAssembly);
218     List<Residue> residueList = rotamerOptimization.getResidues();
219 
220     logger.info("\n Initial Potential Energy:");
221     potentialEnergy.energy(false, true);
222 
223     logger.info("\n Initial Rotamer Torsion Angles:");
224     RotamerLibrary.measureRotamers(residueList, false);
225 
226     // Run the optimization.
227     rotamerOptimization.optimize(manyBodyOptions.getAlgorithm(residueList.size()));
228 
229     boolean isTitrating = false;
230     Set<Atom> excludeAtoms = new HashSet<>();
231     int[] optimalRotamers = rotamerOptimization.getOptimumRotamers();
232     if (manyBodyOptions.getTitration()) {
233       isTitrating = titrationManyBody.excludeExcessAtoms(excludeAtoms, optimalRotamers, residueList);
234     }
235 
236     // Log the final result on rank 0.
237     int rank = Comm.world().rank();
238     if (rank == 0) {
239       logger.info(" Final Minimum Energy");
240       double energy = potentialEnergy.energy(false, true);
241       if (isTitrating) {
242         double phBias = rotamerOptimization.getEnergyExpansion().getTotalRotamerPhBias(residueList,
243             optimalRotamers, titrationPH, manyBodyOptions.getPHRestraint());
244         logger.info(format("\n  Rotamer pH Bias    %16.8f", phBias));
245         logger.info(format("  Potential with Bias%16.8f\n", phBias + energy));
246       }
247 
248       // Prevent residues from being renamed based on the existence of hydrogen
249       // atoms (i.e. hydrogen that are excluded from being written out).
250       properties.setProperty("standardizeAtomNames", "false");
251       File modelFile = saveDirFile(activeAssembly.getFile());
252       PDBFilter pdbFilter = new PDBFilter(modelFile, activeAssembly, activeAssembly.getForceField(),
253           properties);
254       if (manyBodyOptions.getTitration()) {
255         String remark = format("Titration pH: %6.3f", titrationPH);
256         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true, new String[]{remark})) {
257           logger.info(format(" Save failed for %s", activeAssembly));
258         }
259       } else {
260         if (!pdbFilter.writeFile(modelFile, false, excludeAtoms, true, true)) {
261           logger.info(format(" Save failed for %s", activeAssembly));
262         }
263       }
264     }
265 
266     if (manyBodyOptions.getTitration()) {
267       System.clearProperty("manybody-titration");
268     }
269 
270     return this;
271   }
272 
273   /**
274    * Get ManyBodyOptions to access script parameters.
275    */
276   public ManyBodyOptions getManyBodyOptions() {
277     return manyBodyOptions;
278   }
279 
280   /**
281    * Returns the potential energy of the active assembly. Used during testing assertions.
282    * @return potentialEnergy Potential energy of the active assembly.
283    */
284   public ForceFieldEnergy getPotential() {
285     return potentialEnergy;
286   }
287 
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public List<Potential> getPotentials() {
293     List<Potential> potentials;
294     if (potentialEnergy == null) {
295       potentials = Collections.emptyList();
296     } else {
297       potentials = Collections.singletonList(potentialEnergy);
298     }
299     return potentials;
300   }
301 
302 }