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-2024.
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.xray;
39  
40  import static ffx.numerics.math.ScalarMath.b2u;
41  import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
42  import static ffx.utilities.Constants.kB;
43  import static java.lang.String.format;
44  import static java.util.Arrays.fill;
45  
46  import ffx.algorithms.AlgorithmListener;
47  import ffx.algorithms.dynamics.thermostats.Thermostat;
48  import ffx.crystal.Crystal;
49  import ffx.crystal.CrystalPotential;
50  import ffx.numerics.Potential;
51  import ffx.potential.ForceFieldEnergy;
52  import ffx.potential.MolecularAssembly;
53  import ffx.potential.bonded.Atom;
54  import ffx.potential.bonded.Atom.Descriptions;
55  import ffx.potential.bonded.LambdaInterface;
56  import ffx.potential.bonded.Molecule;
57  import ffx.potential.bonded.Residue;
58  import ffx.potential.parameters.ForceField;
59  import ffx.realspace.RealSpaceData;
60  import ffx.realspace.RealSpaceEnergy;
61  import ffx.xray.RefinementMinimize.RefinementMode;
62  import java.util.Arrays;
63  import java.util.List;
64  import java.util.logging.Level;
65  import java.util.logging.Logger;
66  import java.util.stream.Collectors;
67  import java.util.stream.Stream;
68  
69  /**
70   * Combine the X-ray target and chemical potential energy using the {@link
71   * ffx.crystal.CrystalPotential} interface
72   *
73   * @author Timothy D. Fenn
74   * @author Michael J. Schnieders
75   * @since 1.0
76   */
77  public class RefinementEnergy implements LambdaInterface, CrystalPotential, AlgorithmListener {
78  
79    private static final Logger logger = Logger.getLogger(RefinementEnergy.class.getName());
80  
81    /** MolecularAssembly instances being refined. */
82    private final MolecularAssembly[] molecularAssemblies;
83    /** Container to huge experimental data. */
84    private final DataContainer data;
85    /** An array of atoms being refined. */
86    private final Atom[] atomArray;
87    /** The number of atoms being refined. */
88    private final int nAtoms;
89    /** An array of active atoms. */
90    private final Atom[] activeAtomArray;
91    /** An array of XYZIndex values. */
92    private final List<List<Integer>> xIndex;
93    /** The refinement mode being used. */
94    private final RefinementMode refinementMode;
95    /** Atomic coordinates for computing the chemical energy. */
96    private final double[][] xChemical;
97    /** Array for storing chemical gradient. */
98    private final double[][] gChemical;
99    /** A thermostat instance. */
100   protected Thermostat thermostat;
101   /** The number of XYZ coordinates being refined. */
102   int nXYZ;
103   /** The number of b-factor parameters being refined. */
104   int nBFactor;
105   /** The number of occupancy parameters being refined. */
106   int nOccupancy;
107   /** Compute fast varying forces, slowly varying forces, or both. */
108   private STATE state = STATE.BOTH;
109   /** The Potential based on experimental data. */
110   private CrystalPotential dataEnergy;
111   /** The total number of parameters being refined. */
112   private int n;
113   /** The kT scale factor. */
114   private double kTScale;
115   /** Array for storing the experimental gradient. */
116   private double[] gXray;
117   /** Total potential energy. */
118   private double totalEnergy;
119   /** Optimization scale factors. */
120   private double[] optimizationScaling;
121   /** Print a file if there is an error in the energy. */
122   private boolean printOnFailure;
123 
124   /**
125    * RefinementEnergy Constructor.
126    *
127    * @param data input {@link DiffractionData data} for refinement
128    * @param refinementMode {@link ffx.xray.RefinementMinimize.RefinementMode} for refinement
129    */
130   public RefinementEnergy(DataContainer data, RefinementMode refinementMode) {
131     this(data, refinementMode, null);
132   }
133 
134   /**
135    * RefinementEnergy Constructor.
136    *
137    * @param data input {@link DiffractionData data} for refinement
138    * @param refinementMode {@link ffx.xray.RefinementMinimize.RefinementMode} for refinement
139    * @param optimizationScaling scaling of refinement parameters
140    */
141   @SuppressWarnings("fallthrough")
142   public RefinementEnergy(
143       DataContainer data, RefinementMode refinementMode, double[] optimizationScaling) {
144 
145     this.data = data;
146     this.refinementMode = refinementMode;
147     this.optimizationScaling = optimizationScaling;
148     molecularAssemblies = data.getMolecularAssemblies();
149     atomArray = data.getAtomArray();
150     nAtoms = atomArray.length;
151 
152     RefinementModel refinementModel = data.getRefinementModel();
153 
154     // Determine if lambda derivatives are needed.
155     ForceField forceField = molecularAssemblies[0].getForceField();
156     // boolean lambdaTerm = forceField.getBoolean("LAMBDATERM", false);
157     printOnFailure = forceField.getBoolean("PRINT_ON_FAILURE", true);
158 
159     // Fill an active atom array.
160     int count = 0;
161     int nUse = 0;
162     for (Atom a : atomArray) {
163       if (a.isActive()) {
164         count++;
165       }
166       if (a.getUse()) {
167         nUse++;
168       }
169     }
170     int nActive = count;
171     activeAtomArray = new Atom[count];
172     count = 0;
173     for (Atom a : atomArray) {
174       if (a.isActive()) {
175         activeAtomArray[count++] = a;
176       }
177     }
178 
179     xIndex = refinementModel.getxIndex();
180     thermostat = null;
181     kTScale = 1.0;
182 
183     // Determine size of fit.
184     n = nXYZ = nBFactor = nOccupancy = 0;
185     switch (refinementMode) {
186       case COORDINATES:
187         nXYZ = nActive * 3;
188         break;
189       case COORDINATES_AND_BFACTORS:
190       case COORDINATES_AND_OCCUPANCIES:
191       case COORDINATES_AND_BFACTORS_AND_OCCUPANCIES:
192         // Coordinate params.
193         nXYZ = nActive * 3;
194       case BFACTORS:
195       case OCCUPANCIES:
196       case BFACTORS_AND_OCCUPANCIES:
197         if (data instanceof DiffractionData) {
198           DiffractionData diffractionData = (DiffractionData) data;
199           // bfactor params
200           if (refinementMode == RefinementMode.BFACTORS
201               || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
202               || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
203               || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
204             int resnum = -1;
205             int nres = diffractionData.getnResidueBFactor() + 1;
206             for (Atom a : atomArray) {
207               // Ignore hydrogens and atoms that are not active.
208               if (a.getAtomicNumber() == 1 || !a.isActive()) {
209                 continue;
210               }
211               if (a.getAnisou(null) == null) {
212                 if (diffractionData.isAddAnisou()) {
213                   logger.info(
214                       format(" Adding ANISOU to %s.", a.describe(Descriptions.Resnum_Name)));
215                   double[] anisou = new double[6];
216                   double u = b2u(a.getTempFactor());
217                   anisou[0] = anisou[1] = anisou[2] = u;
218                   anisou[3] = anisou[4] = anisou[5] = 0.0;
219                   a.setAnisou(anisou);
220                   nBFactor += 6;
221                 } else if (diffractionData.isResidueBFactor()) {
222                   if (resnum != a.getResidueNumber()) {
223                     if (nres >= diffractionData.getnResidueBFactor()) {
224                       nBFactor++;
225                       nres = 1;
226                     } else {
227                       nres++;
228                     }
229                     resnum = a.getResidueNumber();
230                   }
231                 } else {
232                   nBFactor++;
233                 }
234               } else {
235                 nBFactor += 6;
236               }
237             }
238             if (diffractionData.isResidueBFactor()) {
239               if (nres < diffractionData.getnResidueBFactor()) {
240                 nBFactor--;
241               }
242             }
243           }
244 
245           // Occupancy params.
246           if (refinementMode == RefinementMode.OCCUPANCIES
247               || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
248               || refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
249               || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
250             for (List<Residue> list : refinementModel.getAltResidues()) {
251               nOccupancy += list.size();
252             }
253             for (List<Molecule> list : refinementModel.getAltMolecules()) {
254               nOccupancy += list.size();
255             }
256             if (nActive != nAtoms) {
257               logger.severe(" Occupancy refinement is not supported with inactive atoms.");
258             }
259           }
260         } else {
261           logger.severe(" Refinement method not supported for this data type!");
262         }
263         break;
264     }
265 
266     logger.info(
267         String.format(
268             "\n RefinementEnergy\n  Number of atoms:\t\t%d\n  Atoms being used:  \t\t%d\n  Active atoms: \t\t%d",
269             nAtoms, nUse, nActive));
270 
271     n = nXYZ + nBFactor + nOccupancy;
272     logger.info(
273         String.format(
274             "  Number of variables:\t\t%d (nXYZ %d, nB %d, nOcc %d)\n",
275             n, nXYZ, nBFactor, nOccupancy));
276 
277     // initialize force field and Xray energies
278     for (MolecularAssembly molecularAssembly : molecularAssemblies) {
279       ForceFieldEnergy forceFieldEnergy = molecularAssembly.getPotentialEnergy();
280       if (forceFieldEnergy == null) {
281         forceFieldEnergy = ForceFieldEnergy.energyFactory(molecularAssembly);
282         molecularAssembly.setPotential(forceFieldEnergy);
283       }
284     }
285 
286     if (data instanceof DiffractionData) {
287       DiffractionData diffractionData = (DiffractionData) data;
288       if (!diffractionData.getScaled()[0]) {
289         diffractionData.printStats();
290       }
291       dataEnergy = new XRayEnergy(diffractionData, nXYZ, nBFactor, nOccupancy, refinementMode);
292     } else if (data instanceof RealSpaceData) {
293       RealSpaceData realSpaceData = (RealSpaceData) data;
294       dataEnergy = new RealSpaceEnergy(realSpaceData, nXYZ, 0, 0, refinementMode);
295     }
296 
297     int assemblySize = molecularAssemblies.length;
298     xChemical = new double[assemblySize][];
299     gChemical = new double[assemblySize][];
300     for (int i = 0; i < assemblySize; i++) {
301       int len = molecularAssemblies[i].getActiveAtomArray().length * 3;
302       xChemical[i] = new double[len];
303       gChemical[i] = new double[len];
304     }
305   }
306 
307   /** {@inheritDoc} */
308   @Override
309   public boolean algorithmUpdate(MolecularAssembly active) {
310     if (thermostat != null) {
311       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
312     }
313     logger.info(" kTscale: " + kTScale);
314     logger.info(data.printEnergyUpdate());
315     return true;
316   }
317 
318   /** {@inheritDoc} */
319   @Override
320   public boolean destroy() {
321     return dataEnergy.destroy();
322   }
323 
324   /** {@inheritDoc} */
325   @Override
326   public double energy(double[] x) {
327     return energy(x, false);
328   }
329 
330   /** {@inheritDoc} */
331   @Override
332   public double energy(double[] x, boolean print) {
333     double weight = data.getWeight();
334     double e = 0.0;
335 
336     if (thermostat != null) {
337       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
338     }
339 
340     unscaleCoordinates(x);
341 
342     int assemblysize = molecularAssemblies.length;
343     switch (refinementMode) {
344       case COORDINATES:
345         // Compute the chemical energy.
346         for (int i = 0; i < assemblysize; i++) {
347           ForceFieldEnergy fe = molecularAssemblies[i].getPotentialEnergy();
348           getAssemblyi(i, x, xChemical[i]);
349           double curE = fe.energy(xChemical[i], print);
350           e += (curE - e) / (i + 1);
351         }
352         double chemE = e;
353 
354         e = chemE * kTScale;
355 
356         // Compute the X-ray target energy.
357         double xE = dataEnergy.energy(x, print);
358         e += weight * xE;
359         break;
360       case BFACTORS:
361       case OCCUPANCIES:
362       case BFACTORS_AND_OCCUPANCIES:
363         // Compute the X-ray target energy and gradient.
364         e = dataEnergy.energy(x, print);
365         break;
366       case COORDINATES_AND_BFACTORS:
367       case COORDINATES_AND_OCCUPANCIES:
368       case COORDINATES_AND_BFACTORS_AND_OCCUPANCIES:
369         // Compute the chemical energy and gradient.
370         for (int i = 0; i < assemblysize; i++) {
371           ForceFieldEnergy fe = molecularAssemblies[i].getPotentialEnergy();
372           getAssemblyi(i, x, xChemical[i]);
373           double curE = fe.energy(xChemical[i], print);
374           e += (curE - e) / (i + 1);
375         }
376         e += weight * dataEnergy.energy(x, print);
377         break;
378       default:
379         String message = "Unknown refinement mode.";
380         logger.log(Level.SEVERE, message);
381     }
382 
383     scaleCoordinates(x);
384 
385     totalEnergy = e;
386     return e;
387   }
388 
389   /**
390    * {@inheritDoc}
391    *
392    * <p>Implementation of the {@link CrystalPotential} interface for the RefinementEnergy.
393    */
394   @Override
395   public double energyAndGradient(double[] x, double[] g) {
396     return energyAndGradient(x, g, false);
397   }
398 
399   /**
400    * {@inheritDoc}
401    *
402    * <p>Implementation of the {@link CrystalPotential} interface for the RefinementEnergy.
403    */
404   @Override
405   public double energyAndGradient(double[] x, double[] g, boolean print) {
406     double weight = data.getWeight();
407     double e = 0.0;
408     fill(g, 0.0);
409 
410     if (thermostat != null) {
411       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
412     }
413 
414     unscaleCoordinates(x);
415 
416     int assemblysize = molecularAssemblies.length;
417     switch (refinementMode) {
418       case COORDINATES:
419         // Compute the chemical energy and gradient.
420         for (int i = 0; i < assemblysize; i++) {
421           ForceFieldEnergy fe = molecularAssemblies[i].getPotentialEnergy();
422           getAssemblyi(i, x, xChemical[i]);
423           double curE = fe.energyAndGradient(xChemical[i], gChemical[i], print);
424           e += (curE - e) / (i + 1);
425           setAssemblyi(i, g, gChemical[i]);
426         }
427         double chemE = e;
428 
429         e = chemE * kTScale;
430         // normalize gradients for multiple-counted atoms
431         if (assemblysize > 1) {
432           for (int i = 0; i < nXYZ; i++) {
433             g[i] /= assemblysize;
434           }
435         }
436         for (int i = 0; i < nXYZ; i++) {
437           g[i] *= kTScale;
438         }
439 
440         // Compute the X-ray target energy and gradient.
441         if (gXray == null || gXray.length != nXYZ) {
442           gXray = new double[nXYZ];
443         }
444         double xE = dataEnergy.energyAndGradient(x, gXray);
445         // System.out.println("Xray E: " + xE + " scaled Xray E: " + weight * xE);
446         e += weight * xE;
447 
448         // Add the chemical and X-ray gradients.
449         for (int i = 0; i < nXYZ; i++) {
450           g[i] += weight * gXray[i];
451         }
452         break;
453       case BFACTORS:
454       case OCCUPANCIES:
455       case BFACTORS_AND_OCCUPANCIES:
456         // Compute the X-ray target energy and gradient.
457         e = dataEnergy.energyAndGradient(x, g);
458         break;
459       case COORDINATES_AND_BFACTORS:
460       case COORDINATES_AND_OCCUPANCIES:
461       case COORDINATES_AND_BFACTORS_AND_OCCUPANCIES:
462         // Compute the chemical energy and gradient.
463         for (int i = 0; i < assemblysize; i++) {
464           ForceFieldEnergy fe = molecularAssemblies[i].getPotentialEnergy();
465           getAssemblyi(i, x, xChemical[i]);
466           double curE = fe.energyAndGradient(xChemical[i], gChemical[i], print);
467           e += (curE - e) / (i + 1);
468           setAssemblyi(i, g, gChemical[i]);
469         }
470         // normalize gradients for multiple-counted atoms
471         if (assemblysize > 1) {
472           for (int i = 0; i < nXYZ; i++) {
473             g[i] /= assemblysize;
474           }
475         }
476 
477         // Compute the X-ray target energy and gradient.
478         if (gXray == null || gXray.length != n) {
479           gXray = new double[n];
480         }
481         e += weight * dataEnergy.energyAndGradient(x, gXray);
482 
483         // Add the chemical and X-ray gradients.
484         for (int i = 0; i < nXYZ; i++) {
485           g[i] += weight * gXray[i];
486         }
487 
488         // bfactors, occ
489         if (n > nXYZ) {
490           for (int i = nXYZ; i < n; i++) {
491             g[i] = weight * gXray[i];
492           }
493         }
494         break;
495       default:
496         String message = "Unknown refinement mode.";
497         logger.log(Level.SEVERE, message);
498     }
499 
500     scaleCoordinatesAndGradient(x, g);
501 
502     totalEnergy = e;
503     return e;
504   }
505 
506   /** {@inheritDoc} */
507   @Override
508   public double[] getAcceleration(double[] acceleration) {
509     if (this.nBFactor > 0 || this.nOccupancy > 0) {
510       throw new UnsupportedOperationException("Not supported yet.");
511     }
512     int n = getNumberOfVariables();
513     if (acceleration == null || acceleration.length < n) {
514       acceleration = new double[n];
515     }
516     int index = 0;
517     double[] a = new double[3];
518     for (int i = 0; i < nAtoms; i++) {
519       if (atomArray[i].isActive()) {
520         atomArray[i].getAcceleration(a);
521         acceleration[index++] = a[0];
522         acceleration[index++] = a[1];
523         acceleration[index++] = a[2];
524       }
525     }
526     return acceleration;
527   }
528 
529   /**
530    * getActiveAtoms.
531    *
532    * @return an array of {@link ffx.potential.bonded.Atom} objects.
533    */
534   public Atom[] getActiveAtoms() {
535     return activeAtomArray;
536   }
537 
538   /** {@inheritDoc} */
539   @Override
540   public double[] getCoordinates(double[] parameters) {
541     return dataEnergy.getCoordinates(parameters);
542   }
543 
544   /** {@inheritDoc} */
545   @Override
546   public Crystal getCrystal() {
547     return dataEnergy.getCrystal();
548   }
549 
550   /** {@inheritDoc} */
551   @Override
552   public void setCrystal(Crystal crystal) {
553     logger.severe(" RefinementEnergy does implement setCrystal yet.");
554   }
555 
556   /**
557    * Getter for the field <code>dataEnergy</code>.
558    *
559    * @return a {@link ffx.crystal.CrystalPotential} object.
560    */
561   public CrystalPotential getDataEnergy() {
562     return dataEnergy;
563   }
564 
565   /** {@inheritDoc} */
566   @Override
567   public STATE getEnergyTermState() {
568     return state;
569   }
570 
571   /** {@inheritDoc} */
572   @Override
573   public void setEnergyTermState(STATE state) {
574     this.state = state;
575     for (MolecularAssembly molecularAssembly : molecularAssemblies) {
576       ForceFieldEnergy fe = molecularAssembly.getPotentialEnergy();
577       fe.setEnergyTermState(state);
578     }
579     dataEnergy.setEnergyTermState(state);
580   }
581 
582   /**
583    * get the current kT scaling weight
584    *
585    * @return kT scale
586    */
587   public double getKTScale() {
588     return this.kTScale;
589   }
590 
591   /**
592    * set the current kT scaling weight
593    *
594    * @param ktscale requested kT scale
595    */
596   public void setKTScale(double ktscale) {
597     this.kTScale = ktscale;
598   }
599 
600   /** {@inheritDoc} */
601   @Override
602   public double getLambda() {
603     double lambda = 1.0;
604     if (data instanceof DiffractionData) {
605       XRayEnergy xRayEnergy = (XRayEnergy) dataEnergy;
606       lambda = xRayEnergy.getLambda();
607     } else if (data instanceof RealSpaceData) {
608       RealSpaceEnergy realSpaceEnergy = (RealSpaceEnergy) dataEnergy;
609       lambda = realSpaceEnergy.getLambda();
610     }
611     return lambda;
612   }
613 
614   /** {@inheritDoc} */
615   @Override
616   public void setLambda(double lambda) {
617     for (MolecularAssembly molecularAssembly : molecularAssemblies) {
618       ForceFieldEnergy forceFieldEnergy = molecularAssembly.getPotentialEnergy();
619       forceFieldEnergy.setLambda(lambda);
620     }
621     if (data instanceof DiffractionData) {
622       XRayEnergy xRayEnergy = (XRayEnergy) dataEnergy;
623       xRayEnergy.setLambda(lambda);
624     } else if (data instanceof RealSpaceData) {
625       RealSpaceEnergy realSpaceEnergy = (RealSpaceEnergy) dataEnergy;
626       realSpaceEnergy.setLambda(lambda);
627     }
628   }
629 
630   /** {@inheritDoc} */
631   @Override
632   public double[] getMass() {
633     return dataEnergy.getMass();
634   }
635 
636   /** {@inheritDoc} */
637   @Override
638   public int getNumberOfVariables() {
639     return dataEnergy.getNumberOfVariables();
640   }
641 
642   /** {@inheritDoc} */
643   @Override
644   public double[] getPreviousAcceleration(double[] previousAcceleration) {
645     if (this.nBFactor > 0 || this.nOccupancy > 0) {
646       throw new UnsupportedOperationException("Not supported yet.");
647     }
648     int n = getNumberOfVariables();
649     if (previousAcceleration == null || previousAcceleration.length < n) {
650       previousAcceleration = new double[n];
651     }
652     int index = 0;
653     double[] a = new double[3];
654     for (int i = 0; i < nAtoms; i++) {
655       if (atomArray[i].isActive()) {
656         atomArray[i].getPreviousAcceleration(a);
657         previousAcceleration[index++] = a[0];
658         previousAcceleration[index++] = a[1];
659         previousAcceleration[index++] = a[2];
660       }
661     }
662     return previousAcceleration;
663   }
664 
665   /**
666    * Getter for the field <code>printOnFailure</code>.
667    *
668    * @return a boolean.
669    */
670   public boolean getPrintOnFailure() {
671     return printOnFailure;
672   }
673 
674   /** {@inheritDoc} */
675   @Override
676   public double[] getScaling() {
677     return optimizationScaling;
678   }
679 
680   /** {@inheritDoc} */
681   @Override
682   public void setScaling(double[] scaling) {
683     optimizationScaling = scaling;
684   }
685 
686   /**
687    * Getter for the field <code>thermostat</code>.
688    *
689    * @return a {@link ffx.algorithms.dynamics.thermostats.Thermostat} object.
690    */
691   public Thermostat getThermostat() {
692     return thermostat;
693   }
694 
695   /**
696    * Setter for the field <code>thermostat</code>.
697    *
698    * @param thermostat a {@link ffx.algorithms.dynamics.thermostats.Thermostat} object.
699    */
700   public void setThermostat(Thermostat thermostat) {
701     this.thermostat = thermostat;
702   }
703 
704   /** {@inheritDoc} */
705   @Override
706   public double getTotalEnergy() {
707     return totalEnergy;
708   }
709 
710   @Override
711   public List<Potential> getUnderlyingPotentials() {
712     Stream<Potential> directPEs =
713         Arrays.stream(molecularAssemblies).map(MolecularAssembly::getPotentialEnergy);
714     Stream<Potential> allPEs =
715         Arrays.stream(molecularAssemblies)
716             .map(MolecularAssembly::getPotentialEnergy)
717             .map(Potential::getUnderlyingPotentials)
718             .flatMap(List::stream);
719     return Stream.concat(directPEs, allPEs).collect(Collectors.toList());
720   }
721 
722   /**
723    * {@inheritDoc}
724    *
725    * <p>Return a reference to each variables type.
726    */
727   @Override
728   public VARIABLE_TYPE[] getVariableTypes() {
729     return dataEnergy.getVariableTypes();
730   }
731 
732   /** {@inheritDoc} */
733   @Override
734   public double[] getVelocity(double[] velocity) {
735     if (this.nBFactor > 0 || this.nOccupancy > 0) {
736       throw new UnsupportedOperationException("Not supported yet.");
737     }
738     int n = getNumberOfVariables();
739     if (velocity == null || velocity.length < n) {
740       velocity = new double[n];
741     }
742     int index = 0;
743     double[] v = new double[3];
744     for (int i = 0; i < nAtoms; i++) {
745       Atom a = atomArray[i];
746       if (a.isActive()) {
747         a.getVelocity(v);
748         velocity[index++] = v[0];
749         velocity[index++] = v[1];
750         velocity[index++] = v[2];
751       }
752     }
753     return velocity;
754   }
755 
756   /**
757    * Get the current data weight (wA).
758    *
759    * @return weight wA
760    */
761   public double getXWeight() {
762     return data.getWeight();
763   }
764 
765   /** {@inheritDoc} */
766   @Override
767   public double getd2EdL2() {
768     double d2EdL2 = 0.0;
769     if (thermostat != null) {
770       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
771     }
772     int assemblysize = molecularAssemblies.length;
773 
774     // Compute the chemical energy and gradient.
775     for (int i = 0; i < assemblysize; i++) {
776       ForceFieldEnergy forceFieldEnergy = molecularAssemblies[i].getPotentialEnergy();
777       double curE = forceFieldEnergy.getd2EdL2();
778       d2EdL2 += (curE - d2EdL2) / (i + 1);
779     }
780     d2EdL2 *= kTScale;
781 
782     // No 2nd derivative for scattering term.
783     return d2EdL2;
784   }
785 
786   /** {@inheritDoc} */
787   @Override
788   public double getdEdL() {
789     double dEdL = 0.0;
790     if (thermostat != null) {
791       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
792     }
793     int assemblysize = molecularAssemblies.length;
794 
795     // Compute the chemical energy and gradient.
796     for (int i = 0; i < assemblysize; i++) {
797       ForceFieldEnergy forceFieldEnergy = molecularAssemblies[i].getPotentialEnergy();
798       double curdEdL = forceFieldEnergy.getdEdL();
799       dEdL += (curdEdL - dEdL) / (i + 1);
800     }
801     dEdL *= kTScale;
802     double weight = data.getWeight();
803     if (data instanceof DiffractionData) {
804       XRayEnergy xRayEnergy = (XRayEnergy) dataEnergy;
805       dEdL += weight * xRayEnergy.getdEdL();
806     } else if (data instanceof RealSpaceData) {
807       RealSpaceEnergy realSpaceEnergy = (RealSpaceEnergy) dataEnergy;
808       dEdL += weight * realSpaceEnergy.getdEdL();
809     }
810     return dEdL;
811   }
812 
813   /** {@inheritDoc} */
814   @Override
815   public void getdEdXdL(double[] gradient) {
816     double weight = data.getWeight();
817     if (thermostat != null) {
818       kTScale = KCAL_TO_GRAM_ANG2_PER_PS2 / (thermostat.getTargetTemperature() * kB);
819     }
820     int assemblysize = molecularAssemblies.length;
821 
822     // Compute the chemical energy and gradient.
823     for (int i = 0; i < assemblysize; i++) {
824       ForceFieldEnergy forcefieldEnergy = molecularAssemblies[i].getPotentialEnergy();
825       Arrays.fill(gChemical[i], 0.0);
826       forcefieldEnergy.getdEdXdL(gChemical[i]);
827     }
828     for (int i = 0; i < assemblysize; i++) {
829       for (int j = 0; j < nXYZ; j++) {
830         gradient[j] += gChemical[i][j];
831       }
832     }
833 
834     // Normalize gradients for multiple-counted atoms.
835     if (assemblysize > 1) {
836       for (int i = 0; i < nXYZ; i++) {
837         gradient[i] /= assemblysize;
838       }
839     }
840     for (int i = 0; i < nXYZ; i++) {
841       gradient[i] *= kTScale;
842     }
843 
844     // Compute the X-ray target energy and gradient.
845     if (gXray == null || gXray.length != nXYZ) {
846       gXray = new double[nXYZ];
847     } else {
848       for (int j = 0; j < nXYZ; j++) {
849         gXray[j] = 0.0;
850       }
851     }
852     if (data instanceof DiffractionData) {
853       XRayEnergy xRayEnergy = (XRayEnergy) dataEnergy;
854       xRayEnergy.getdEdXdL(gXray);
855     } else if (data instanceof RealSpaceData) {
856       RealSpaceEnergy realSpaceEnergy = (RealSpaceEnergy) dataEnergy;
857       realSpaceEnergy.getdEdXdL(gXray);
858     }
859 
860     // Add the chemical and X-ray gradients.
861     for (int i = 0; i < nXYZ; i++) {
862       gradient[i] += weight * gXray[i];
863     }
864   }
865 
866   /** {@inheritDoc} */
867   @Override
868   public void setAcceleration(double[] acceleration) {
869     if (this.nBFactor > 0 || this.nOccupancy > 0) {
870       throw new UnsupportedOperationException("Not supported yet.");
871     }
872     if (acceleration == null) {
873       return;
874     }
875     int index = 0;
876     double[] accel = new double[3];
877     for (int i = 0; i < nAtoms; i++) {
878       if (atomArray[i].isActive()) {
879         accel[0] = acceleration[index++];
880         accel[1] = acceleration[index++];
881         accel[2] = acceleration[index++];
882         atomArray[i].setAcceleration(accel);
883       }
884     }
885   }
886 
887   /** {@inheritDoc} */
888   @Override
889   public void setPreviousAcceleration(double[] previousAcceleration) {
890     if (this.nBFactor > 0 || this.nOccupancy > 0) {
891       throw new UnsupportedOperationException("Not supported yet.");
892     }
893     if (previousAcceleration == null) {
894       return;
895     }
896     int index = 0;
897     double[] prev = new double[3];
898     for (int i = 0; i < nAtoms; i++) {
899       if (atomArray[i].isActive()) {
900         prev[0] = previousAcceleration[index++];
901         prev[1] = previousAcceleration[index++];
902         prev[2] = previousAcceleration[index++];
903         atomArray[i].setPreviousAcceleration(prev);
904       }
905     }
906   }
907 
908   /**
909    * Sets the printOnFailure flag; if override is true, over-rides any existing property.
910    * Essentially sets the default value of printOnFailure for an algorithm. For example, rotamer
911    * optimization will generally run into force field issues in the normal course of execution as it
912    * tries unphysical self and 2-Body configurations, so the algorithm should not print out a large
913    * number of error PDBs.
914    *
915    * @param onFail To set
916    * @param override Override properties
917    */
918   public void setPrintOnFailure(boolean onFail, boolean override) {
919     if (override) {
920       // Ignore any pre-existing value
921       printOnFailure = onFail;
922     } else {
923       try {
924         molecularAssemblies[0].getForceField().getBoolean("PRINT_ON_FAILURE");
925         /*
926          * If the call was successful, the property was explicitly set
927          * somewhere and should be kept. If an exception was thrown, the
928          * property was never set explicitly, so over-write.
929          */
930       } catch (Exception ex) {
931         printOnFailure = onFail;
932       }
933     }
934   }
935 
936   /** {@inheritDoc} */
937   @Override
938   public void setVelocity(double[] velocity) {
939     if (this.nBFactor > 0 || this.nOccupancy > 0) {
940       throw new UnsupportedOperationException("Not supported yet.");
941     }
942     if (velocity == null) {
943       return;
944     }
945     int index = 0;
946     double[] vel = new double[3];
947     for (int i = 0; i < nAtoms; i++) {
948       if (atomArray[i].isActive()) {
949         vel[0] = velocity[index++];
950         vel[1] = velocity[index++];
951         vel[2] = velocity[index++];
952         atomArray[i].setVelocity(vel);
953       }
954     }
955   }
956 
957   /**
958    * Get the MolecularAssembly associated with index i of n; put in xChem.
959    *
960    * @param i The desired MolecularAssembly index for xChem.
961    * @param x All parameters.
962    * @param xChem The xChem parameters for the particular MolecularAssembly that will be passed to
963    *     {@link ffx.potential.ForceFieldEnergy}.
964    */
965   private void getAssemblyi(int i, double[] x, double[] xChem) {
966     assert (x != null && xChem != null);
967     for (int j = 0; j < xChem.length; j += 3) {
968       int index = j / 3;
969       //int aindex = xIndex[i].get(index) * 3;
970       int aindex = xIndex.get(i).get(index) * 3;
971       xChem[j] = x[aindex];
972       xChem[j + 1] = x[aindex + 1];
973       xChem[j + 2] = x[aindex + 2];
974     }
975   }
976 
977   /**
978    * Set the MolecularAssembly associated with index i of n; put in x.
979    *
980    * @param i the desired MolecularAssembly index for "setting" x.
981    * @param x All parameters.
982    * @param xChem The xChem parameters for the particular MolecularAssembly that will be passed to
983    *     {@link ffx.potential.ForceFieldEnergy}.
984    */
985   private void setAssemblyi(int i, double[] x, double[] xChem) {
986     assert (x != null && xChem != null);
987     for (int j = 0; j < xChem.length; j += 3) {
988       int index = j / 3;
989       // int aindex = xIndex[i].get(index) * 3;
990       int aindex = xIndex.get(i).get(index) * 3;
991       x[aindex] += xChem[j];
992       x[aindex + 1] += xChem[j + 1];
993       x[aindex + 2] += xChem[j + 2];
994     }
995   }
996 }