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