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.xray;
39  
40  import ffx.crystal.Crystal;
41  import ffx.crystal.CrystalPotential;
42  import ffx.potential.bonded.Atom;
43  import ffx.potential.bonded.Bond;
44  import ffx.potential.bonded.LambdaInterface;
45  import ffx.potential.bonded.Molecule;
46  import ffx.potential.bonded.Residue;
47  import ffx.potential.parameters.ForceField;
48  import ffx.xray.RefinementMinimize.RefinementMode;
49  
50  import java.util.List;
51  import java.util.logging.Logger;
52  
53  import static ffx.numerics.math.MatrixMath.determinant3;
54  import static ffx.numerics.math.ScalarMath.b2u;
55  import static ffx.numerics.math.ScalarMath.u2b;
56  import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
57  import static ffx.utilities.Constants.kB;
58  import static java.lang.String.format;
59  import static java.lang.System.arraycopy;
60  import static java.util.Arrays.fill;
61  import static org.apache.commons.math3.util.FastMath.PI;
62  
63  /**
64   * Combine the X-ray target and chemical potential energy.
65   *
66   * @author Timothy D. Fenn
67   * @author Michael J. Schnieders
68   * @since 1.0
69   */
70  public class XRayEnergy implements LambdaInterface, CrystalPotential {
71  
72    private static final Logger logger = Logger.getLogger(XRayEnergy.class.getName());
73    private static final double kBkcal = kB / KCAL_TO_GRAM_ANG2_PER_PS2;
74    private static final double eightPI2 = 8.0 * PI * PI;
75    private static final double eightPI23 = eightPI2 * eightPI2 * eightPI2;
76  
77    private final DiffractionData diffractionData;
78    private final RefinementModel refinementModel;
79    private final Atom[] activeAtomArray;
80    private final int nAtoms;
81    private final double bMass;
82    private final double kTbNonzero;
83    private final double kTbSimWeight;
84    private final double occMass;
85    private final boolean lambdaTerm;
86    private final double[] g2;
87    private final double[] dUdXdL;
88    protected double lambda = 1.0;
89    private int nXYZ;
90    private int nB;
91    private int nOCC;
92    private RefinementMode refinementMode;
93    private boolean refineXYZ = false;
94    private boolean refineOCC = false;
95    private boolean refineB = false;
96    private boolean xrayTerms = true;
97    private boolean restraintTerms = true;
98    private double[] optimizationScaling = null;
99    private double totalEnergy;
100   private double dEdL;
101   private STATE state = STATE.BOTH;
102 
103   /**
104    * Diffraction data energy target
105    *
106    * @param diffractionData {@link ffx.xray.DiffractionData} object to associate with the target
107    * @param nXYZ number of xyz parameters
108    * @param nB number of b factor parameters
109    * @param nOCC number of occupancy parameters
110    * @param refinementMode the {@link ffx.xray.RefinementMinimize.RefinementMode} type of refinement
111    *     requested
112    */
113   public XRayEnergy(
114       DiffractionData diffractionData, int nXYZ, int nB, int nOCC, RefinementMode refinementMode) {
115     this.diffractionData = diffractionData;
116     this.refinementModel = diffractionData.getRefinementModel();
117     this.refinementMode = refinementMode;
118     this.nXYZ = nXYZ;
119     this.nB = nB;
120     this.nOCC = nOCC;
121 
122     Atom[] atomArray = refinementModel.getTotalAtomArray();
123     this.nAtoms = atomArray.length;
124 
125     bMass = diffractionData.getbMass();
126     double temperature = 50.0;
127     kTbNonzero = 0.5 * kBkcal * temperature * diffractionData.getbNonZeroWeight();
128     kTbSimWeight = kBkcal * temperature * diffractionData.getbSimWeight();
129     occMass = diffractionData.getOccMass();
130 
131     ForceField forceField = diffractionData.getAssembly()[0].getForceField();
132     lambdaTerm = forceField.getBoolean("LAMBDATERM", false);
133 
134     // Fill an active atom array.
135     int count = 0;
136     for (Atom a : atomArray) {
137       if (a.isActive()) {
138         count++;
139       }
140     }
141     activeAtomArray = new Atom[count];
142     count = 0;
143     for (Atom a : atomArray) {
144       if (a.isActive()) {
145         activeAtomArray[count++] = a;
146       }
147     }
148 
149     dUdXdL = new double[count * 3];
150     g2 = new double[count * 3];
151 
152     setRefinementBooleans();
153 
154     if (refineB) {
155       logger.info(" B-Factor Refinement Parameters");
156       logger.info("  Temperature:                 " + temperature);
157       logger.info("  Non-zero restraint weight:   " + diffractionData.getbNonZeroWeight());
158       logger.info("  Similarity restraint weight: " + diffractionData.getbSimWeight());
159     }
160   }
161 
162   /** {@inheritDoc} */
163   @Override
164   public boolean destroy() {
165     return diffractionData.destroy();
166   }
167 
168   /** {@inheritDoc} */
169   @Override
170   public double energy(double[] x) {
171     double e = 0.0;
172 
173     // Unscale the coordinates.
174     unscaleCoordinates(x);
175 
176     if (refineXYZ) {
177       // update coordinates
178       diffractionData.setFFTCoordinates(x);
179     }
180     if (refineB) {
181       // update B factors
182       setBFactors(x);
183     }
184     if (refineOCC) {
185       // update occupancies
186       setOccupancies(x);
187     }
188 
189     if (xrayTerms) {
190 
191       if (lambdaTerm) {
192         diffractionData.setLambdaTerm(false);
193       }
194 
195       // Compute new structure factors.
196       diffractionData.computeAtomicDensity();
197       // Compute crystal likelihood.
198       e = diffractionData.computeLikelihood();
199 
200       if (lambdaTerm) {
201 
202         // Turn off all atoms scaled by lambda.
203         diffractionData.setLambdaTerm(true);
204 
205         // Compute new structure factors.
206         diffractionData.computeAtomicDensity();
207 
208         // Compute crystal likelihood.
209         double e2 = diffractionData.computeLikelihood();
210 
211         dEdL = e - e2;
212 
213         e = lambda * e + (1.0 - lambda) * e2;
214 
215         diffractionData.setLambdaTerm(false);
216       }
217     }
218 
219     if (restraintTerms) {
220       if (refineB) {
221         // add B restraints
222         e += getBFactorRestraints();
223       }
224     }
225 
226     // Scale the coordinates and gradients.
227     scaleCoordinates(x);
228 
229     totalEnergy = e;
230     return e;
231   }
232 
233   /** {@inheritDoc} */
234   @Override
235   public double energyAndGradient(double[] x, double[] g) {
236     double e = 0.0;
237 
238     // Unscale the coordinates.
239     unscaleCoordinates(x);
240 
241     if (refineXYZ) {
242       for (Atom a : activeAtomArray) {
243         a.setXYZGradient(0.0, 0.0, 0.0);
244         a.setLambdaXYZGradient(0.0, 0.0, 0.0);
245       }
246 
247       // update coordinates
248       diffractionData.setFFTCoordinates(x);
249     }
250 
251     if (refineB) {
252       for (Atom a : activeAtomArray) {
253         a.setTempFactorGradient(0.0);
254         if (a.getAnisou(null) != null) {
255           if (a.getAnisouGradient(null) == null) {
256             double[] ganisou = new double[6];
257             a.setAnisouGradient(ganisou);
258           } else {
259             double[] ganisou = a.getAnisouGradient(null);
260             ganisou[0] = ganisou[1] = ganisou[2] = 0.0;
261             ganisou[3] = ganisou[4] = ganisou[5] = 0.0;
262             a.setAnisouGradient(ganisou);
263           }
264         }
265       }
266 
267       // update B factors
268       setBFactors(x);
269     }
270 
271     if (refineOCC) {
272       for (Atom a : activeAtomArray) {
273         a.setOccupancyGradient(0.0);
274       }
275 
276       // update occupancies
277       setOccupancies(x);
278     }
279 
280     if (xrayTerms) {
281 
282       if (lambdaTerm) {
283         diffractionData.setLambdaTerm(false);
284       }
285 
286       // compute new structure factors
287       diffractionData.computeAtomicDensity();
288 
289       // compute crystal likelihood
290       e = diffractionData.computeLikelihood();
291 
292       // compute the crystal gradients
293       diffractionData.computeAtomicGradients(refinementMode);
294 
295       if (refineXYZ) {
296         // pack gradients into gradient array
297         getXYZGradients(g);
298       }
299 
300       if (lambdaTerm) {
301 
302         int n = dUdXdL.length;
303         arraycopy(g, 0, dUdXdL, 0, n);
304 
305         for (Atom a : activeAtomArray) {
306           a.setXYZGradient(0.0, 0.0, 0.0);
307           a.setLambdaXYZGradient(0.0, 0.0, 0.0);
308         }
309 
310         // Turn off all atoms scaled by lambda.
311         diffractionData.setLambdaTerm(true);
312 
313         // Compute new structure factors.
314         diffractionData.computeAtomicDensity();
315 
316         // Compute crystal likelihood.
317         double e2 = diffractionData.computeLikelihood();
318 
319         // compute the crystal gradients
320         diffractionData.computeAtomicGradients(refinementMode);
321 
322         dEdL = e - e2;
323         e = lambda * e + (1.0 - lambda) * e2;
324         getXYZGradients(g2);
325         for (int i = 0; i < g.length; i++) {
326           dUdXdL[i] -= g2[i];
327           g[i] = lambda * g[i] + (1.0 - lambda) * g2[i];
328         }
329 
330         diffractionData.setLambdaTerm(false);
331       }
332     }
333 
334     if (restraintTerms) {
335       if (refineB) {
336         // add B restraints
337         e += getBFactorRestraints();
338 
339         // pack gradients into gradient array
340         getBFactorGradients(g);
341       }
342 
343       if (refineOCC) {
344         // pack gradients into gradient array
345         getOccupancyGradients(g);
346       }
347     }
348 
349     // Scale the coordinates and gradients.
350     scaleCoordinatesAndGradient(x, g);
351 
352     totalEnergy = e;
353     return e;
354   }
355 
356   /** {@inheritDoc} */
357   @Override
358   public double[] getAcceleration(double[] acceleration) {
359     throw new UnsupportedOperationException("Not supported yet.");
360   }
361 
362   /** {@inheritDoc} */
363   @Override
364   public double[] getCoordinates(double[] x) {
365     assert (x != null);
366     double[] xyz = new double[3];
367     int index = 0;
368     fill(x, 0.0);
369 
370     if (refineXYZ) {
371       for (Atom a : activeAtomArray) {
372         a.getXYZ(xyz);
373         x[index++] = xyz[0];
374         x[index++] = xyz[1];
375         x[index++] = xyz[2];
376       }
377     }
378 
379     if (refineB) {
380       double[] anisou = null;
381       int resnum = -1;
382       int nat = 0;
383       int nres = diffractionData.getnResidueBFactor() + 1;
384       for (Atom a : activeAtomArray) {
385         // ignore hydrogens!!!
386         if (a.getAtomicNumber() == 1) {
387           continue;
388         }
389         if (a.getAnisou(null) != null) {
390           anisou = a.getAnisou(anisou);
391           x[index++] = anisou[0];
392           x[index++] = anisou[1];
393           x[index++] = anisou[2];
394           x[index++] = anisou[3];
395           x[index++] = anisou[4];
396           x[index++] = anisou[5];
397         } else if (diffractionData.isResidueBFactor()) {
398           if (resnum != a.getResidueNumber()) {
399             if (nres >= diffractionData.getnResidueBFactor()) {
400               if (resnum > -1 && index < nXYZ + nB - 1) {
401                 x[index] /= nat;
402                 index++;
403               }
404               nat = 1;
405               nres = 1;
406             } else {
407               nres++;
408               nat++;
409             }
410             x[index] += a.getTempFactor();
411             resnum = a.getResidueNumber();
412           } else {
413             x[index] += a.getTempFactor();
414             nat++;
415           }
416         } else {
417           x[index++] = a.getTempFactor();
418         }
419       }
420 
421       if (diffractionData.isResidueBFactor()) {
422         if (nat > 1) {
423           x[index] /= nat;
424         }
425       }
426     }
427 
428     if (refineOCC) {
429       for (List<Residue> list : refinementModel.getAltResidues()) {
430         for (Residue r : list) {
431           for (Atom a : r.getAtomList()) {
432             if (a.getOccupancy() < 1.0) {
433               x[index++] = a.getOccupancy();
434               break;
435             }
436           }
437         }
438       }
439       for (List<Molecule> list : refinementModel.getAltMolecules()) {
440         for (Molecule m : list) {
441           for (Atom a : m.getAtomList()) {
442             if (a.getOccupancy() < 1.0) {
443               x[index++] = a.getOccupancy();
444               break;
445             }
446           }
447         }
448       }
449     }
450 
451     return x;
452   }
453 
454   /** {@inheritDoc} */
455   @Override
456   public Crystal getCrystal() {
457     return diffractionData.getCrystal()[0];
458   }
459 
460   /** {@inheritDoc} */
461   @Override
462   public void setCrystal(Crystal crystal) {
463     logger.severe(" XRayEnergy does implement setCrystal yet.");
464   }
465 
466   /** {@inheritDoc} */
467   @Override
468   public STATE getEnergyTermState() {
469     return state;
470   }
471 
472   /** {@inheritDoc} */
473   @Override
474   public void setEnergyTermState(STATE state) {
475     this.state = state;
476     switch (state) {
477       case FAST:
478         xrayTerms = false;
479         restraintTerms = true;
480         break;
481       case SLOW:
482         xrayTerms = true;
483         restraintTerms = false;
484         break;
485       default:
486         xrayTerms = true;
487         restraintTerms = true;
488     }
489   }
490 
491   /** {@inheritDoc} */
492   @Override
493   public double getLambda() {
494     return lambda;
495   }
496 
497   /** {@inheritDoc} */
498   @Override
499   public void setLambda(double lambda) {
500     if (lambda <= 1.0 && lambda >= 0.0) {
501       this.lambda = lambda;
502     } else {
503       String message = format("Lambda value %8.3f is not in the range [0..1].", lambda);
504       logger.warning(message);
505     }
506   }
507 
508   /** {@inheritDoc} */
509   @Override
510   public double[] getMass() {
511     double[] mass = new double[nXYZ + nB + nOCC];
512     int i = 0;
513     if (refineXYZ) {
514       for (Atom a : activeAtomArray) {
515         double m = a.getMass();
516         mass[i++] = m;
517         mass[i++] = m;
518         mass[i++] = m;
519       }
520     }
521 
522     if (refineB) {
523       for (int j = i; j < nXYZ + nB; i++, j++) {
524         mass[j] = bMass;
525       }
526     }
527 
528     if (refineOCC) {
529       for (int j = i; j < nXYZ + nB + nOCC; i++, j++) {
530         mass[j] = occMass;
531       }
532     }
533     return mass;
534   }
535 
536   /**
537    * get the number of B factor parameters being fit
538    *
539    * @return the number of B factor parameters
540    */
541   public int getNB() {
542     return nB;
543   }
544 
545   /**
546    * set the number of B factor parameters
547    *
548    * @param nB requested number of B factor parameters
549    */
550   public void setNB(int nB) {
551     this.nB = nB;
552   }
553 
554   /**
555    * get the number of occupancy parameters being fit
556    *
557    * @return the number of occupancy parameters
558    */
559   public int getNOcc() {
560     return nOCC;
561   }
562 
563   /**
564    * set the number of occupancy parameters
565    *
566    * @param nOCC requested number of occupancy parameters
567    */
568   public void setNOcc(int nOCC) {
569     this.nOCC = nOCC;
570   }
571 
572   /**
573    * Get the number of xyz parameters being fit.
574    *
575    * @return the number of xyz parameters
576    */
577   public int getNXYZ() {
578     return nXYZ;
579   }
580 
581   /**
582    * set the number of xyz parameters
583    *
584    * @param nXYZ requested number of xyz parameters
585    */
586   public void setNXYZ(int nXYZ) {
587     this.nXYZ = nXYZ;
588   }
589 
590   /** {@inheritDoc} */
591   @Override
592   public int getNumberOfVariables() {
593     return nXYZ + nB + nOCC;
594   }
595 
596   /** {@inheritDoc} */
597   @Override
598   public double[] getPreviousAcceleration(double[] previousAcceleration) {
599     throw new UnsupportedOperationException("Not supported yet.");
600   }
601 
602   /**
603    * Getter for the field <code>refinementMode</code>.
604    *
605    * @return a {@link ffx.xray.RefinementMinimize.RefinementMode} object.
606    */
607   public RefinementMode getRefinementMode() {
608     return refinementMode;
609   }
610 
611   /**
612    * Setter for the field <code>refinementMode</code>.
613    *
614    * @param refinementmode a {@link ffx.xray.RefinementMinimize.RefinementMode} object.
615    */
616   public void setRefinementMode(RefinementMode refinementmode) {
617     this.refinementMode = refinementmode;
618     setRefinementBooleans();
619   }
620 
621   /** {@inheritDoc} */
622   @Override
623   public double[] getScaling() {
624     return optimizationScaling;
625   }
626 
627   /** {@inheritDoc} */
628   @Override
629   public void setScaling(double[] scaling) {
630     optimizationScaling = scaling;
631   }
632 
633   /** {@inheritDoc} */
634   @Override
635   public double getTotalEnergy() {
636     return totalEnergy;
637   }
638 
639   /**
640    * {@inheritDoc}
641    *
642    * <p>Return a reference to each variables type.
643    */
644   @Override
645   public VARIABLE_TYPE[] getVariableTypes() {
646     VARIABLE_TYPE[] vtypes = new VARIABLE_TYPE[nXYZ + nB + nOCC];
647     int i = 0;
648     if (refineXYZ) {
649       for (Atom a : activeAtomArray) {
650         vtypes[i++] = VARIABLE_TYPE.X;
651         vtypes[i++] = VARIABLE_TYPE.Y;
652         vtypes[i++] = VARIABLE_TYPE.Z;
653       }
654     }
655 
656     if (refineB) {
657       for (int j = i; j < nXYZ + nB; i++, j++) {
658         vtypes[j] = VARIABLE_TYPE.OTHER;
659       }
660     }
661 
662     if (refineOCC) {
663       for (int j = i; j < nXYZ + nB + nOCC; i++, j++) {
664         vtypes[j] = VARIABLE_TYPE.OTHER;
665       }
666     }
667     return vtypes;
668   }
669 
670   /** {@inheritDoc} */
671   @Override
672   public double[] getVelocity(double[] velocity) {
673     throw new UnsupportedOperationException("Not supported yet.");
674   }
675 
676   /** {@inheritDoc} */
677   @Override
678   public double getd2EdL2() {
679     return 0.0;
680   }
681 
682   /** {@inheritDoc} */
683   @Override
684   public double getdEdL() {
685     return dEdL;
686   }
687 
688   /** {@inheritDoc} */
689   @Override
690   public void getdEdXdL(double[] gradient) {
691     int n = dUdXdL.length;
692     arraycopy(dUdXdL, 0, gradient, 0, n);
693   }
694 
695   /** {@inheritDoc} */
696   @Override
697   public void setAcceleration(double[] acceleration) {
698     throw new UnsupportedOperationException("Not supported yet.");
699   }
700 
701   /**
702    * set atomic xyz coordinates based on current position
703    *
704    * @param x current parameters to set coordinates with
705    */
706   public void setCoordinates(double[] x) {
707     assert (x != null);
708     double[] xyz = new double[3];
709     int index = 0;
710     for (Atom a : activeAtomArray) {
711       xyz[0] = x[index++];
712       xyz[1] = x[index++];
713       xyz[2] = x[index++];
714       a.moveTo(xyz);
715     }
716   }
717 
718   /**
719    * set atom occupancies based on current position
720    *
721    * @param x current parameters to set occupancies with
722    */
723   public void setOccupancies(double[] x) {
724     double occ;
725     int index = nXYZ + nB;
726     for (List<Residue> list : refinementModel.getAltResidues()) {
727       for (Residue r : list) {
728         occ = x[index++];
729         for (Atom a : r.getAtomList()) {
730           if (a.getOccupancy() < 1.0) {
731             a.setOccupancy(occ);
732           }
733         }
734       }
735     }
736     for (List<Molecule> list : refinementModel.getAltMolecules()) {
737       for (Molecule m : list) {
738         occ = x[index++];
739         for (Atom a : m.getAtomList()) {
740           if (a.getOccupancy() < 1.0) {
741             a.setOccupancy(occ);
742           }
743         }
744       }
745     }
746   }
747 
748   /** {@inheritDoc} */
749   @Override
750   public void setPreviousAcceleration(double[] previousAcceleration) {
751     throw new UnsupportedOperationException("Not supported yet.");
752   }
753 
754   /** {@inheritDoc} */
755   @Override
756   public void setVelocity(double[] velocity) {
757     throw new UnsupportedOperationException("Not supported yet.");
758   }
759 
760   /**
761    * if the refinement mode has changed, this should be called to update which parameters are being
762    * fit
763    */
764   private void setRefinementBooleans() {
765     // reset, if previously set
766     refineXYZ = false;
767     refineB = false;
768     refineOCC = false;
769 
770     if (refinementMode == RefinementMode.COORDINATES
771         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
772         || refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
773         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
774       refineXYZ = true;
775     }
776 
777     if (refinementMode == RefinementMode.BFACTORS
778         || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
779         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
780         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
781       refineB = true;
782     }
783 
784     if (refinementMode == RefinementMode.OCCUPANCIES
785         || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
786         || refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
787         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
788       refineOCC = true;
789     }
790   }
791 
792   /**
793    * fill gradient array with B factor gradients
794    *
795    * @param g array to add gradients to
796    */
797   private void getBFactorGradients(double[] g) {
798     assert (g != null);
799     double[] grad = null;
800     int index = nXYZ;
801     int resnum = -1;
802     int nres = diffractionData.getnResidueBFactor() + 1;
803     for (Atom a : activeAtomArray) {
804       // ignore hydrogens!!!
805       if (a.getAtomicNumber() == 1) {
806         continue;
807       }
808       if (a.getAnisou(null) != null) {
809         grad = a.getAnisouGradient(grad);
810         g[index++] = grad[0];
811         g[index++] = grad[1];
812         g[index++] = grad[2];
813         g[index++] = grad[3];
814         g[index++] = grad[4];
815         g[index++] = grad[5];
816       } else if (diffractionData.isResidueBFactor()) {
817         if (resnum != a.getResidueNumber()) {
818           if (nres >= diffractionData.getnResidueBFactor()) {
819             if (resnum > -1 && index < nXYZ + nB - 1) {
820               index++;
821             }
822             nres = 1;
823           } else {
824             nres++;
825           }
826           g[index] += a.getTempFactorGradient();
827           resnum = a.getResidueNumber();
828         } else {
829           g[index] += a.getTempFactorGradient();
830         }
831       } else {
832         g[index++] = a.getTempFactorGradient();
833       }
834     }
835   }
836 
837   /**
838    * Fill gradient array with occupancy gradients. Note: this also acts to constrain the occupancies
839    * by moving the gradient vector COM to zero
840    *
841    * @param g array to add gradients to
842    */
843   private void getOccupancyGradients(double[] g) {
844     double ave;
845     int index = nXYZ + nB;
846 
847     // First: Alternate Residues
848     for (List<Residue> list : refinementModel.getAltResidues()) {
849       ave = 0.0;
850       for (Residue r : list) {
851         for (Atom a : r.getAtomList()) {
852           if (a.getOccupancy() < 1.0) {
853             ave += a.getOccupancyGradient();
854           }
855         }
856       }
857       /*
858        Should this be normalized with respect to number of atoms in residue in addition
859        to the number of conformers?
860       */
861       ave /= list.size();
862       for (Residue r : list) {
863         for (Atom a : r.getAtomList()) {
864           if (a.getOccupancy() < 1.0) {
865             g[index] += a.getOccupancyGradient();
866           }
867         }
868         if (list.size() > 1) {
869           // Subtract average to move COM to zero
870           g[index] -= ave;
871         }
872         index++;
873       }
874     }
875 
876     // Now the molecules (HETATMs).
877     for (List<Molecule> list : refinementModel.getAltMolecules()) {
878       ave = 0.0;
879       for (Molecule m : list) {
880         for (Atom a : m.getAtomList()) {
881           if (a.getOccupancy() < 1.0) {
882             ave += a.getOccupancyGradient();
883           }
884         }
885       }
886       ave /= list.size();
887       for (Molecule m : list) {
888         for (Atom a : m.getAtomList()) {
889           if (a.getOccupancy() < 1.0) {
890             g[index] += a.getOccupancyGradient();
891           }
892         }
893         if (list.size() > 1) {
894           g[index] -= ave;
895         }
896         index++;
897       }
898     }
899   }
900 
901   /**
902    * Fill gradient array with atomic coordinate partial derivatives.
903    *
904    * @param g gradient array
905    */
906   private void getXYZGradients(double[] g) {
907     assert (g != null);
908     double[] grad = new double[3];
909     int index = 0;
910     for (Atom a : activeAtomArray) {
911       a.getXYZGradient(grad);
912       g[index++] = grad[0];
913       g[index++] = grad[1];
914       g[index++] = grad[2];
915     }
916   }
917 
918   /**
919    * set atomic B factors based on current position
920    *
921    * @param x current parameters to set B factors with
922    */
923   private void setBFactors(double[] x) {
924     double[] tmpanisou = new double[6];
925     int index = nXYZ;
926     int nneg = 0;
927     int resnum = -1;
928     int nres = diffractionData.getnResidueBFactor() + 1;
929     for (Atom a : activeAtomArray) {
930       // ignore hydrogens!!!
931       if (a.getAtomicNumber() == 1) {
932         continue;
933       }
934       if (a.getAnisou(null) == null) {
935         double biso = x[index];
936         if (diffractionData.isResidueBFactor()) {
937           if (resnum != a.getResidueNumber()) {
938             if (nres >= diffractionData.getnResidueBFactor()) {
939               if (resnum > -1 && index < nXYZ + nB - 1) {
940                 index++;
941                 biso = x[index];
942               }
943               nres = 1;
944             } else {
945               nres++;
946             }
947             resnum = a.getResidueNumber();
948           }
949         } else {
950           index++;
951         }
952 
953         if (biso > 0.0) {
954           a.setTempFactor(biso);
955         } else {
956           nneg++;
957           a.setTempFactor(0.01);
958           if (nneg < 5) {
959             logger.info(" Isotropic atom: " + a + " negative B factor");
960           }
961         }
962       } else {
963         double[] anisou = a.getAnisou(null);
964         tmpanisou[0] = x[index++];
965         tmpanisou[1] = x[index++];
966         tmpanisou[2] = x[index++];
967         tmpanisou[3] = x[index++];
968         tmpanisou[4] = x[index++];
969         tmpanisou[5] = x[index++];
970         double det = determinant3(tmpanisou);
971         if (det > 0.0) {
972           arraycopy(tmpanisou, 0, anisou, 0, 6);
973           a.setAnisou(anisou);
974           det = Math.pow(det, 0.3333);
975           a.setTempFactor(u2b(det));
976         } else {
977           nneg++;
978           a.setTempFactor(0.01);
979           anisou[0] = anisou[1] = anisou[2] = b2u(0.01);
980           anisou[3] = anisou[4] = anisou[5] = 0.0;
981           a.setAnisou(anisou);
982           if (nneg < 5) {
983             logger.info(" Anisotropic atom: " + a + " negative ANISOU");
984           }
985         }
986       }
987     }
988 
989     if (nneg > 0) {
990       logger.info(
991           " "
992               + nneg
993               + " of "
994               + nAtoms
995               + " atoms with negative B factors! Attempting to correct.\n  (If this problem persists, increase bsimweight)");
996     }
997 
998     // set hydrogen based on bonded atom
999     for (Atom a : activeAtomArray) {
1000       if (a.getAtomicNumber() == 1) {
1001         Atom b = a.getBonds().get(0).get1_2(a);
1002         a.setTempFactor(b.getTempFactor());
1003       }
1004     }
1005   }
1006 
1007   /**
1008    * determine similarity and non-zero B factor restraints (done independently of
1009    * getBFactorGradients), affects atomic gradients
1010    *
1011    * @return energy of the restraint
1012    */
1013   private double getBFactorRestraints() {
1014     Atom a1, a2;
1015     double b1, b2, bdiff;
1016     double[] anisou1 = null;
1017     double[] anisou2 = null;
1018     double gradb;
1019     double det1, det2;
1020     double[] gradu = new double[6];
1021     double e = 0.0;
1022 
1023     for (Atom a : activeAtomArray) {
1024       double biso = a.getTempFactor();
1025       // Ignore hydrogens!!!
1026       if (a.getAtomicNumber() == 1) {
1027         continue;
1028       }
1029 
1030       if (a.getAnisou(null) == null) {
1031         // Isotropic B restraint
1032         // Non-zero restraint: -kTln[Z], Z is ADP partition function
1033         e += -3.0 * kTbNonzero * Math.log(biso / (4.0 * Math.PI));
1034         gradb = -3.0 * kTbNonzero / biso;
1035         a.addToTempFactorGradient(gradb);
1036 
1037         // Similarity harmonic restraint
1038         List<Bond> bonds = a.getBonds();
1039         for (Bond b : bonds) {
1040           if (a.compareTo(b.getAtom(0)) == 0) {
1041             a1 = b.getAtom(0);
1042             a2 = b.getAtom(1);
1043           } else {
1044             a1 = b.getAtom(1);
1045             a2 = b.getAtom(0);
1046           }
1047           if (a2.getAtomicNumber() == 1) {
1048             continue;
1049           }
1050           if (a2.getIndex() < a1.getIndex()) {
1051             continue;
1052           }
1053           if (!a1.getAltLoc().equals(' ')
1054               && !a1.getAltLoc().equals('A')
1055               && a2.getAltLoc().equals(' ')) {
1056             continue;
1057           }
1058 
1059           b1 = a1.getTempFactor();
1060           b2 = a2.getTempFactor();
1061           // transform B similarity restraints to U scale
1062           bdiff = b2u(b1 - b2);
1063           e += kTbSimWeight * Math.pow(bdiff, 2.0);
1064           gradb = 2.0 * kTbSimWeight * bdiff;
1065           a1.addToTempFactorGradient(gradb);
1066           a2.addToTempFactorGradient(-gradb);
1067         }
1068       } else {
1069         // Anisotropic B restraint
1070         anisou1 = a.getAnisou(anisou1);
1071         det1 = determinant3(anisou1);
1072 
1073         // Non-zero restraint: -kTln[Z], Z is ADP partition function
1074         e += u2b(-kTbNonzero * Math.log(det1 * eightPI2 * Math.PI));
1075         gradu[0] = u2b(-kTbNonzero * ((anisou1[1] * anisou1[2] - anisou1[5] * anisou1[5]) / det1));
1076         gradu[1] = u2b(-kTbNonzero * ((anisou1[0] * anisou1[2] - anisou1[4] * anisou1[4]) / det1));
1077         gradu[2] = u2b(-kTbNonzero * ((anisou1[0] * anisou1[1] - anisou1[3] * anisou1[3]) / det1));
1078         gradu[3] =
1079             u2b(-kTbNonzero * ((2.0 * (anisou1[4] * anisou1[5] - anisou1[3] * anisou1[2])) / det1));
1080         gradu[4] =
1081             u2b(-kTbNonzero * ((2.0 * (anisou1[3] * anisou1[5] - anisou1[4] * anisou1[1])) / det1));
1082         gradu[5] =
1083             u2b(-kTbNonzero * ((2.0 * (anisou1[3] * anisou1[4] - anisou1[5] * anisou1[0])) / det1));
1084         a.addToAnisouGradient(gradu);
1085 
1086         // Similarity harmonic restraint based on determinants
1087         List<Bond> bonds = a.getBonds();
1088         for (Bond b : bonds) {
1089           if (a.compareTo(b.getAtom(0)) == 0) {
1090             a1 = b.getAtom(0);
1091             a2 = b.getAtom(1);
1092           } else {
1093             a1 = b.getAtom(1);
1094             a2 = b.getAtom(0);
1095           }
1096           if (a2.getAtomicNumber() == 1) {
1097             continue;
1098           }
1099           if (a2.getIndex() < a1.getIndex()) {
1100             continue;
1101           }
1102           if (a2.getAnisou(null) == null) {
1103             continue;
1104           }
1105           if (!a1.getAltLoc().equals(' ')
1106               && !a1.getAltLoc().equals('A')
1107               && a2.getAltLoc().equals(' ')) {
1108             continue;
1109           }
1110 
1111           anisou2 = a2.getAnisou(anisou2);
1112           det2 = determinant3(anisou2);
1113           bdiff = det1 - det2;
1114           e += eightPI23 * kTbSimWeight * Math.pow(bdiff, 2.0);
1115           gradb = eightPI23 * 2.0 * kTbSimWeight * bdiff;
1116 
1117           // parent atom
1118           gradu[0] = gradb * (anisou1[1] * anisou1[2] - anisou1[5] * anisou1[5]);
1119           gradu[1] = gradb * (anisou1[0] * anisou1[2] - anisou1[4] * anisou1[4]);
1120           gradu[2] = gradb * (anisou1[0] * anisou1[1] - anisou1[3] * anisou1[3]);
1121           gradu[3] = gradb * (2.0 * (anisou1[4] * anisou1[5] - anisou1[3] * anisou1[2]));
1122           gradu[4] = gradb * (2.0 * (anisou1[3] * anisou1[5] - anisou1[4] * anisou1[1]));
1123           gradu[5] = gradb * (2.0 * (anisou1[3] * anisou1[4] - anisou1[5] * anisou1[0]));
1124           a1.addToAnisouGradient(gradu);
1125 
1126           // bonded atom
1127           gradu[0] = gradb * (anisou2[5] * anisou2[5] - anisou2[1] * anisou2[2]);
1128           gradu[1] = gradb * (anisou2[4] * anisou2[4] - anisou2[0] * anisou2[2]);
1129           gradu[2] = gradb * (anisou2[3] * anisou2[3] - anisou2[0] * anisou2[1]);
1130           gradu[3] = gradb * (2.0 * (anisou2[3] * anisou2[2] - anisou2[4] * anisou2[5]));
1131           gradu[4] = gradb * (2.0 * (anisou2[4] * anisou2[1] - anisou2[3] * anisou2[5]));
1132           gradu[5] = gradb * (2.0 * (anisou2[5] * anisou2[0] - anisou2[3] * anisou2[4]));
1133           a2.addToAnisouGradient(gradu);
1134         }
1135       }
1136     }
1137     return e;
1138   }
1139 }