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.potential.terms;
39  
40  import edu.rit.pj.IntegerForLoop;
41  import edu.rit.pj.IntegerSchedule;
42  import edu.rit.pj.ParallelRegion;
43  import edu.rit.pj.ParallelTeam;
44  import ffx.numerics.Potential;
45  import ffx.numerics.atomic.AtomicDoubleArray;
46  import ffx.numerics.atomic.AtomicDoubleArray3D;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.BondedTerm;
50  import ffx.potential.parameters.ForceField;
51  
52  import java.util.ArrayList;
53  import java.util.Collections;
54  import java.util.List;
55  import java.util.logging.Logger;
56  
57  import static ffx.potential.parameters.ForceField.toEnumForm;
58  import static java.lang.String.format;
59  
60  public class EnergyTermRegion extends ParallelRegion {
61  
62    private static final Logger logger = Logger.getLogger(EnergyTermRegion.class.getName());
63  
64    /**
65     * The atoms in the MolecularAssembly.
66     */
67    private final Atom[] atoms;
68    /**
69     * The number of atoms in the MolecularAssembly.
70     */
71    private final int nAtoms;
72    /**
73     * If true, the gradient will be computed.
74     */
75    private boolean gradient = false;
76    /**
77     * If true, the gradient for each atom instance will be initialized to zero.
78     */
79    private boolean initAtomGradients = true;
80  
81    /**
82     * The state of the potential energy term, which can be either
83     * BOTH, FAST or SLOW. If SLOW, no bonded terms are evaluated.
84     */
85    private Potential.STATE state = Potential.STATE.BOTH;
86  
87    /*
88     * The logic here deals with how DualTopologyEnergy (DTE) works.
89     * If this ForceFieldEnergy (FFE) is not part of a DTE, lambdaBondedTerms is always false, and the term is always evaluated.
90     *
91     * Most bonded terms should be at full-strength, regardless of lambda, "complemented" to 1.0*strength.
92     * Outside FFE, DTE scales the initial, !lambdaBondedTerms evaluation by f(lambda).
93     *
94     * In the case of a bonded term lacking any softcore atoms, this will be externally complemented by the other FFE topology.
95     * If there is internal lambda-scaling, that will separately apply at both ends.
96     *
97     * In the case of a bonded term with a softcore atom, it's a bit trickier.
98     * If it's unscaled by lambda, it needs to be internally complemented; we re-evaluate it with lambdaBondedTerms true.
99     * This second evaluation is scaled by DTE by a factor of f(1-lambda), and becomes "restraintEnergy".
100    * If it is scaled internally by lambda, we assume that the energy term is not meant to be internally complemented.
101    * In that case, we skip evaluation into restraintEnergy.
102    */
103 
104   /**
105    * Alchemical atoms will not be checked for restraints.
106    */
107   protected boolean checkAlchemicalAtoms = true;
108   /**
109    * Indicates only bonded energy terms effected by Lambda should be evaluated.
110    */
111   protected boolean lambdaBondedTerms = false;
112   /**
113    * Indicates all bonded energy terms should be evaluated if lambdaBondedTerms is true.
114    */
115   protected boolean lambdaAllBondedTerms = false;
116   /**
117    * List of energy terms that are based on local bonded interactions.
118    */
119   private final List<EnergyTerm> energyTerms = new ArrayList<>();
120   /**
121    * The bonded energy terms will be executed in parallel using these loops.
122    */
123   private final BondedTermLoop[] bondedTermLoops;
124   /**
125    * Loops to initialize the gradient to zero.
126    */
127   private final GradInitLoop[] gradInitLoops;
128   /**
129    * Loops to reduce the gradient after computation.
130    */
131   private final GradReduceLoop[] gradReduceLoops;
132   /**
133    * The gradient for each atom, indexed by atom index and thread index.
134    */
135   private final AtomicDoubleArray3D grad;
136   /**
137    * The lambda gradient for each atom, indexed by atom index and thread index.
138    * This is only used if lambdaTerm is true.
139    */
140   private final AtomicDoubleArray3D lambdaGrad;
141 
142   /**
143    * Constructor for BondedRegion.
144    *
145    * @param molecularAssembly The MolecularAssembly that this region will operate on.
146    * @param lambdaTerm        If true, the lambda gradient will be computed.
147    * @param parallelTeam      The ParallelTeam that this region will run in.
148    */
149   public EnergyTermRegion(ParallelTeam parallelTeam,
150                           MolecularAssembly molecularAssembly,
151                           boolean lambdaTerm) {
152     this.atoms = molecularAssembly.getAtomArray();
153     this.nAtoms = atoms.length;
154 
155     // Define how the gradient will be accumulated.
156     AtomicDoubleArray.AtomicDoubleArrayImpl atomicDoubleArrayImpl = AtomicDoubleArray.AtomicDoubleArrayImpl.MULTI;
157     ForceField forceField = molecularAssembly.getForceField();
158 
159     int nThreads = parallelTeam.getThreadCount();
160     bondedTermLoops = new BondedTermLoop[nThreads];
161     gradInitLoops = new GradInitLoop[nThreads];
162     gradReduceLoops = new GradReduceLoop[nThreads];
163     for (int i = 0; i < nThreads; i++) {
164       bondedTermLoops[i] = new BondedTermLoop();
165       gradInitLoops[i] = new GradInitLoop();
166       gradReduceLoops[i] = new GradReduceLoop();
167     }
168 
169     String value = forceField.getString("ARRAY_REDUCTION", "MULTI");
170     try {
171       atomicDoubleArrayImpl = AtomicDoubleArray.AtomicDoubleArrayImpl.valueOf(toEnumForm(value));
172     } catch (Exception e) {
173       logger.info(format(" Unrecognized ARRAY-REDUCTION %s; defaulting to %s", value,
174           atomicDoubleArrayImpl));
175     }
176     logger.fine(format("  Bonded using %s arrays.", atomicDoubleArrayImpl));
177 
178     int nAtoms = molecularAssembly.getAtomArray().length;
179     grad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, nThreads);
180     if (lambdaTerm) {
181       lambdaGrad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, nThreads);
182     } else {
183       lambdaGrad = null;
184     }
185   }
186 
187   public void setCheckAlchemicalAtoms(boolean checkAlchemicalAtoms) {
188     this.checkAlchemicalAtoms = checkAlchemicalAtoms;
189   }
190 
191   public boolean getCheckAlchemicalAtoms() {
192     return checkAlchemicalAtoms;
193   }
194 
195   public void setLambdaBondedTerms(boolean lambdaBondedTerms) {
196     this.lambdaBondedTerms = lambdaBondedTerms;
197   }
198 
199   public boolean getLambdaBondedTerms() {
200     return lambdaBondedTerms;
201   }
202 
203   public void setLambdaAllBondedTerms(boolean lambdaAllBondedTerms) {
204     this.lambdaAllBondedTerms = lambdaAllBondedTerms;
205   }
206 
207   public boolean getLambdaAllBondedTerms() {
208     return lambdaAllBondedTerms;
209   }
210 
211   public void setInitAtomGradients(boolean initAtomGradients) {
212     this.initAtomGradients = initAtomGradients;
213   }
214 
215   public boolean getInitAtomGradients() {
216     return initAtomGradients;
217   }
218 
219   /**
220    * Get the total energy of all EnergyTerms.
221    *
222    * @return The total energy computed by summing the energies of all EnergyTerms.
223    */
224   public double getEnergy() {
225     double energy = 0.0;
226     for (EnergyTerm term : energyTerms) {
227       energy += term.getEnergy();
228     }
229     return energy;
230   }
231 
232   /**
233    * Log the details of the energy terms in this bonded region.
234    */
235   public void log() {
236     for (EnergyTerm term : energyTerms) {
237       term.log();
238     }
239   }
240 
241   /**
242    * String representation of this EnergyTermRegion.
243    *
244    * @return A string containing the details of all energy terms.
245    */
246   public String toString() {
247     StringBuilder sb = new StringBuilder();
248     for (EnergyTerm term : energyTerms) {
249       sb.append(term.toString());
250     }
251     return sb.toString();
252   }
253 
254   /**
255    * String representation for PDB Headers
256    *
257    * @return A string containing the details of all energy terms.
258    */
259   public String toPDBString() {
260     StringBuilder sb = new StringBuilder();
261     for (EnergyTerm term : energyTerms) {
262       sb.append(term.toPDBString());
263     }
264     return sb.toString();
265   }
266 
267   /**
268    * Set the state of the potential energy term.
269    *
270    * @param state The new state, which can be either BOTH, FAST or SLOW.
271    */
272   public void setState(Potential.STATE state) {
273     if (state == null) {
274       throw new IllegalArgumentException("Potential state cannot be null.");
275     }
276     this.state = state;
277   }
278 
279   /**
280    * Set whether the gradient will be computed.
281    *
282    * @param gradient If true, compute the gradient.
283    */
284   public void setGradient(boolean gradient) {
285     this.gradient = gradient;
286   }
287 
288   /**
289    * Get whether the gradient will be computed.
290    *
291    * @return true if the gradient will be computed.
292    */
293   public boolean getGradient() {
294     return gradient;
295   }
296 
297   /**
298    * Add a EnergyTerm to this bonded region.
299    *
300    * @param term EnergyTerm to add (ignored if null).
301    * @return true if the term was added.
302    */
303   public boolean addEnergyTerm(EnergyTerm term) {
304     if (term == null) {
305       return false;
306     }
307     return energyTerms.add(term);
308   }
309 
310   /**
311    * Remove an EnergyTerm from this bonded region.
312    *
313    * @param term EnergyTerm to remove (ignored if null).
314    * @return true if the term was present and removed.
315    */
316   public boolean removeEnergyTerm(EnergyTerm term) {
317     if (term == null) {
318       return false;
319     }
320     return energyTerms.remove(term);
321   }
322 
323   /**
324    * Get the BondedEnergyTerm at a specific index.
325    *
326    * @param index Index into the bonded energy terms list.
327    * @return EnergyTerm at the specified index.
328    * @throws IndexOutOfBoundsException if index is invalid.
329    */
330   public EnergyTerm getEnergyTerm(int index) {
331     return energyTerms.get(index);
332   }
333 
334   /**
335    * Get an unmodifiable view of the energy terms.
336    *
337    * @return Unmodifiable list of EnergyTerm.
338    */
339   public List<EnergyTerm> getEnergyTerms() {
340     return Collections.unmodifiableList(energyTerms);
341   }
342 
343   @Override
344   public void start() {
345     // Initialize the BondedEnergyTerms.
346     for (EnergyTerm term : energyTerms) {
347       term.setEnergy(0.0);
348       term.setRMSD(0.0);
349     }
350   }
351 
352   @Override
353   public void run() throws Exception {
354     int threadIndex = getThreadIndex();
355 
356     // Initialize the Gradient to zero.
357     if (gradient) {
358       execute(0, nAtoms - 1, gradInitLoops[threadIndex]);
359     }
360 
361     // Determine if the bonded energy terms should be evaluated.
362     if (state == Potential.STATE.BOTH || state == Potential.STATE.FAST) {
363       // Loop over all BondedEnergyTerms and execute them in parallel.
364       BondedTermLoop bondedTermLoop = bondedTermLoops[threadIndex];
365       for (EnergyTerm term : energyTerms) {
366         if (threadIndex == 0) {
367           term.startTime();
368         }
369         bondedTermLoop.setEnergyTerm(term);
370         execute(0, term.getNumberOfTerms() - 1, bondedTermLoop);
371         if (threadIndex == 0) {
372           term.stopTime();
373         }
374       }
375     }
376 
377     // Reduce the gradients if they were computed.
378     if (gradient) {
379       execute(0, nAtoms - 1, gradReduceLoops[threadIndex]);
380     }
381 
382   }
383 
384   private class BondedTermLoop extends IntegerForLoop {
385 
386     private EnergyTerm energyTerm;
387 
388     public void setEnergyTerm(EnergyTerm terms) {
389       this.energyTerm = terms;
390     }
391 
392     @Override
393     public void run(int first, int last) {
394       int threadIndex = getThreadIndex();
395       double localEnergy = 0.0;
396       double localRMSD = 0.0;
397 
398       BondedTerm[] bondedTerms = energyTerm.getBondedTermsArray();
399       for (int i = first; i <= last; i++) {
400         BondedTerm term = bondedTerms[i];
401         boolean used = true;
402         /*
403          * The logic here deals with how DualTopologyEnergy (DTE) works.
404          * If this ForceFieldEnergy (FFE) is not part of a DTE, lambdaBondedTerms is always false, and the term is always evaluated.
405          *
406          * Most bonded terms should be at full-strength, regardless of lambda, "complemented" to 1.0*strength.
407          * Outside FFE, DTE scales the initial, !lambdaBondedTerms evaluation by f(lambda).
408          *
409          * In the case of a bonded term lacking any softcore atoms, this will be externally complemented by the other FFE topology.
410          * If there is internal lambda-scaling, that will separately apply at both ends.
411          *
412          * In the case of a bonded term with a softcore atom, it's a bit trickier.
413          * If it's unscaled by lambda, it needs to be internally complemented; we re-evaluate it with lambdaBondedTerms true.
414          * This second evaluation is scaled by DTE by a factor of f(1-lambda), and becomes "restraintEnergy".
415          * If it is scaled internally by lambda, we assume that the energy term is not meant to be internally complemented.
416          * In that case, we skip evaluation into restraintEnergy.
417          */
418         if (checkAlchemicalAtoms) {
419           used = !lambdaBondedTerms || lambdaAllBondedTerms || (term.applyLambda() && !term.isLambdaScaled());
420         }
421         if (used) {
422           localEnergy += term.energy(gradient, threadIndex, grad, lambdaGrad);
423           double value = term.getValue();
424           localRMSD += value * value;
425         }
426       }
427 
428       energyTerm.addAndGetEnergy(localEnergy);
429       energyTerm.addAndGetRMSD(localRMSD);
430     }
431 
432   }
433 
434   private class GradInitLoop extends IntegerForLoop {
435 
436     @Override
437     public void run(int first, int last) {
438       int threadID = getThreadIndex();
439       if (gradient) {
440         grad.reset(threadID, first, last);
441         if (initAtomGradients) {
442           for (int i = first; i <= last; i++) {
443             atoms[i].setXYZGradient(0.0, 0.0, 0.0);
444           }
445         }
446       }
447       if (lambdaGrad != null) {
448         lambdaGrad.reset(threadID, first, last);
449         if (initAtomGradients) {
450           for (int i = first; i <= last; i++) {
451             atoms[i].setLambdaXYZGradient(0.0, 0.0, 0.0);
452           }
453         }
454       }
455     }
456 
457     @Override
458     public IntegerSchedule schedule() {
459       return IntegerSchedule.fixed();
460     }
461   }
462 
463   private class GradReduceLoop extends IntegerForLoop {
464 
465     @Override
466     public void run(int first, int last) {
467       if (gradient) {
468         grad.reduce(first, last);
469         for (int i = first; i <= last; i++) {
470           Atom a = atoms[i];
471           a.addToXYZGradient(grad.getX(i), grad.getY(i), grad.getZ(i));
472         }
473       }
474       if (lambdaGrad != null) {
475         lambdaGrad.reduce(first, last);
476         for (int i = first; i <= last; i++) {
477           Atom a = atoms[i];
478           a.addToLambdaXYZGradient(lambdaGrad.getX(i), lambdaGrad.getY(i), lambdaGrad.getZ(i));
479         }
480       }
481     }
482 
483     @Override
484     public IntegerSchedule schedule() {
485       return IntegerSchedule.fixed();
486     }
487   }
488 }