View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2026.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential;
39  
40  import ffx.crystal.Crystal;
41  import ffx.crystal.SpaceGroup;
42  import ffx.numerics.Potential;
43  import ffx.potential.MolecularAssembly.FractionalMode;
44  import ffx.potential.bonded.Atom;
45  
46  import java.util.ArrayList;
47  import java.util.List;
48  import java.util.logging.Logger;
49  
50  import static org.apache.commons.math3.util.FastMath.toDegrees;
51  
52  /**
53   * This class computes the energy and Cartesian coordinate gradient, plus finite difference
54   * derivatives of lattice parameters.
55   *
56   * @author Jooyeon Park
57   * @since 1.0
58   */
59  public class XtalEnergy implements Potential {
60  
61    private static final Logger logger = Logger.getLogger(XtalEnergy.class.getName());
62  
63    /**
64     * MolecularAssembly to compute the crystal energy for.
65     */
66    private final MolecularAssembly molecularAssembly;
67    /**
68     * The ForceFieldEnergy to use.
69     */
70    private final ForceFieldEnergy forceFieldEnergy;
71    /**
72     * Array of active atoms.
73     */
74    private final Atom[] activeAtoms;
75    /**
76     * Number of active atoms.
77     */
78    private final int nActive;
79    /**
80     * Atomic coordinates
81     */
82    private final double[] xyz;
83    private final double[] gr;
84    private final int nParams;
85  
86    private final Crystal crystal;
87    private final boolean latticeOnly;
88    private final VARIABLE_TYPE[] type;
89    private final double[] mass;
90    private final Crystal unitCell;
91    private double[] scaling;
92    private double totalEnergy;
93    private FractionalMode fractionalMode = FractionalMode.OFF;
94  
95    /**
96     * Constructor for XtalEnergy.
97     *
98     * @param forceFieldEnergy  a {@link ffx.potential.ForceFieldEnergy} object.
99     * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
100    */
101   public XtalEnergy(ForceFieldEnergy forceFieldEnergy, MolecularAssembly molecularAssembly) {
102     this(forceFieldEnergy, molecularAssembly, false);
103   }
104 
105   /**
106    * Constructor for XtalEnergy.
107    *
108    * @param forceFieldEnergy  a {@link ffx.potential.ForceFieldEnergy} object.
109    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
110    * @param latticeOnly       if true, only lattice parameters are optimized.
111    */
112   public XtalEnergy(ForceFieldEnergy forceFieldEnergy, MolecularAssembly molecularAssembly, boolean latticeOnly) {
113     this.forceFieldEnergy = forceFieldEnergy;
114     this.molecularAssembly = molecularAssembly;
115     this.latticeOnly = latticeOnly;
116 
117     if (!latticeOnly) {
118       Atom[] atoms = molecularAssembly.getAtomArray();
119       List<Atom> active = new ArrayList<>();
120       for (Atom a : atoms) {
121         if (a.isActive()) {
122           active.add(a);
123         }
124       }
125       nActive = active.size();
126       activeAtoms = active.toArray(new Atom[0]);
127       xyz = new double[3 * nActive];
128       gr = new double[3 * nActive];
129     } else {
130       nActive = 0;
131       activeAtoms = null;
132       xyz = null;
133       gr = null;
134     }
135 
136     nParams = 3 * nActive + 6;
137     crystal = forceFieldEnergy.getCrystal();
138 
139     logger.info(" XtalEnergy Crystal: " + crystal);
140 
141     unitCell = crystal.getUnitCell();
142     type = new VARIABLE_TYPE[nParams];
143     mass = new double[nParams];
144 
145     // Load atomic masses and variable types for atomic coordinates.
146     int index = 0;
147     for (int i = 0; i < nActive; i++) {
148       double m = activeAtoms[i].getMass();
149       mass[index] = m;
150       mass[index + 1] = m;
151       mass[index + 2] = m;
152       type[index] = VARIABLE_TYPE.X;
153       type[index + 1] = VARIABLE_TYPE.Y;
154       type[index + 2] = VARIABLE_TYPE.Z;
155       index += 3;
156     }
157     // Load variable types for lattice parameters.
158     for (int i = nActive * 3; i < nActive * 3 + 6; i++) {
159       mass[i] = 1.0;
160       type[i] = VARIABLE_TYPE.OTHER;
161     }
162   }
163 
164   /**
165    * {@inheritDoc}
166    */
167   @Override
168   public boolean destroy() {
169     return forceFieldEnergy.destroy();
170   }
171 
172   /**
173    * {@inheritDoc}
174    */
175   @Override
176   public double energy(double[] x) {
177 
178     // Un-scale coordinates if applicable.
179     unscaleCoordinates(x);
180 
181     // Set atomic coordinates & lattice parameters.
182     setCoordinates(x);
183 
184     // Compute energy.
185     totalEnergy = forceFieldEnergy.energy(false, false);
186 
187     // Scale coordinates if applicable.
188     scaleCoordinates(x);
189 
190     return totalEnergy;
191   }
192 
193   /**
194    * {@inheritDoc}
195    */
196   @Override
197   public double energyAndGradient(double[] x, double[] g) {
198     // Un-scale coordinates if applicable.
199     unscaleCoordinates(x);
200 
201     // Set atomic coordinates & lattice parameters.
202     setCoordinates(x);
203 
204     if (latticeOnly) {
205       // Only lattice parameters are optimized.
206       totalEnergy = forceFieldEnergy.energy(false, false);
207     } else {
208       // Compute energy and gradient.
209       totalEnergy = forceFieldEnergy.energyAndGradient(xyz, gr);
210 
211     }
212 
213     // Both coordinates and gradient are scaled if applicable.
214     packGradient(x, g);
215 
216     // Calculate finite-difference partial derivatives of lattice parameters.
217     unitCellParameterDerivatives(x, g);
218 
219     return totalEnergy;
220   }
221 
222   /**
223    * {@inheritDoc}
224    */
225   @Override
226   public double[] getAcceleration(double[] acceleration) {
227     throw new UnsupportedOperationException("Not supported yet.");
228   }
229 
230   /**
231    * {@inheritDoc}
232    */
233   @Override
234   public double[] getCoordinates(double[] x) {
235     int n = getNumberOfVariables();
236     if (x == null || x.length < n) {
237       x = new double[n];
238     }
239     int index = 0;
240     for (int i = 0; i < nActive; i++) {
241       Atom a = activeAtoms[i];
242       x[index] = a.getX();
243       index++;
244       x[index] = a.getY();
245       index++;
246       x[index] = a.getZ();
247       index++;
248     }
249     x[index] = unitCell.a;
250     index++;
251     x[index] = unitCell.b;
252     index++;
253     x[index] = unitCell.c;
254     index++;
255     x[index] = unitCell.alpha;
256     index++;
257     x[index] = unitCell.beta;
258     index++;
259     x[index] = unitCell.gamma;
260     return x;
261   }
262 
263   /**
264    * {@inheritDoc}
265    */
266   @Override
267   public STATE getEnergyTermState() {
268     return forceFieldEnergy.getEnergyTermState();
269   }
270 
271   /**
272    * {@inheritDoc}
273    */
274   @Override
275   public void setEnergyTermState(STATE state) {
276     forceFieldEnergy.setEnergyTermState(state);
277   }
278 
279   /**
280    * {@inheritDoc}
281    */
282   @Override
283   public double[] getMass() {
284     return mass;
285   }
286 
287   /**
288    * {@inheritDoc}
289    */
290   @Override
291   public int getNumberOfVariables() {
292     return nParams;
293   }
294 
295   /**
296    * {@inheritDoc}
297    */
298   @Override
299   public double[] getPreviousAcceleration(double[] previousAcceleration) {
300     throw new UnsupportedOperationException("Not supported yet.");
301   }
302 
303   /**
304    * {@inheritDoc}
305    */
306   @Override
307   public double[] getScaling() {
308     return scaling;
309   }
310 
311   /**
312    * {@inheritDoc}
313    */
314   @Override
315   public void setScaling(double[] scaling) {
316     this.scaling = scaling;
317   }
318 
319   /**
320    * {@inheritDoc}
321    */
322   @Override
323   public double getTotalEnergy() {
324     return totalEnergy;
325   }
326 
327   /**
328    * {@inheritDoc}
329    */
330   @Override
331   public VARIABLE_TYPE[] getVariableTypes() {
332     return type;
333   }
334 
335   /**
336    * {@inheritDoc}
337    */
338   @Override
339   public double[] getVelocity(double[] velocity) {
340     throw new UnsupportedOperationException("Not supported yet.");
341   }
342 
343   /**
344    * {@inheritDoc}
345    */
346   @Override
347   public void setAcceleration(double[] acceleration) {
348     throw new UnsupportedOperationException("Not supported yet.");
349   }
350 
351   /**
352    * setFractionalCoordinateMode.
353    *
354    * @param fractionalMode a {@link ffx.potential.MolecularAssembly.FractionalMode} object.
355    */
356   public void setFractionalCoordinateMode(FractionalMode fractionalMode) {
357     this.fractionalMode = fractionalMode;
358     molecularAssembly.setFractionalMode(fractionalMode);
359   }
360 
361   /**
362    * {@inheritDoc}
363    */
364   @Override
365   public void setPreviousAcceleration(double[] previousAcceleration) {
366     throw new UnsupportedOperationException("Not supported yet.");
367   }
368 
369   /**
370    * {@inheritDoc}
371    */
372   @Override
373   public void setVelocity(double[] velocity) {
374     throw new UnsupportedOperationException("Not supported yet.");
375   }
376 
377   /**
378    * Use finite-differences to compute unit cell derivatives.
379    *
380    * @param x Coordinates and unit cell parameters.
381    * @param g gradient.
382    */
383   private void unitCellParameterDerivatives(double[] x, double[] g) {
384     int index = 3 * nActive;
385       double eps = 1.0e-5;
386       double deps = toDegrees(eps);
387       SpaceGroup spaceGroup = crystal.spaceGroup;
388       switch (spaceGroup.latticeSystem) {
389         case TRICLINIC_LATTICE -> {
390           g[index] = finiteDifference(x, index, eps);
391           index++;
392           g[index] = finiteDifference(x, index, eps);
393           index++;
394           g[index] = finiteDifference(x, index, eps);
395           index++;
396           g[index] = finiteDifference(x, index, deps);
397           index++;
398           g[index] = finiteDifference(x, index, deps);
399           index++;
400           g[index] = finiteDifference(x, index, deps);
401         }
402         case MONOCLINIC_LATTICE -> {
403           // alpha = gamma = 90
404           g[index] = finiteDifference(x, index, eps);
405           index++;
406           g[index] = finiteDifference(x, index, eps);
407           index++;
408           g[index] = finiteDifference(x, index, eps);
409           index++;
410           g[index] = 0.0;
411           index++;
412           g[index] = finiteDifference(x, index, deps);
413           index++;
414           g[index] = 0.0;
415         }
416         case ORTHORHOMBIC_LATTICE -> {
417           // alpha = beta = gamma = 90
418           g[index] = finiteDifference(x, index, eps);
419           index++;
420           g[index] = finiteDifference(x, index, eps);
421           index++;
422           g[index] = finiteDifference(x, index, eps);
423           index++;
424           g[index] = 0.0;
425           index++;
426           g[index] = 0.0;
427           index++;
428           g[index] = 0.0;
429         }
430         case TETRAGONAL_LATTICE, // a = b, alpha = beta = gamma = 90
431              HEXAGONAL_LATTICE // a = b, alpha = beta = 90, gamma = 120
432             -> {
433           g[index] = finiteDifference2(x, index, index + 1, eps);
434           index++;
435           g[index] = g[index - 1];
436           index++;
437           g[index] = finiteDifference(x, index, eps);
438           index++;
439           g[index] = 0.0;
440           index++;
441           g[index] = 0.0;
442           index++;
443           g[index] = 0.0;
444         }
445         case RHOMBOHEDRAL_LATTICE -> {
446           // a = b = c, alpha = beta = gamma
447           g[index] = finiteDifference3(x, index, index + 1, index + 2, eps);
448           index++;
449           g[index] = g[index - 1];
450           index++;
451           g[index] = g[index - 2];
452           index++;
453           g[index] = finiteDifference3(x, index, index + 1, index + 2, deps);
454           index++;
455           g[index] = g[index - 1];
456           index++;
457           g[index] = g[index - 2];
458         }
459         // a = b, alpha = beta = 90, gamma = 120
460         case CUBIC_LATTICE -> {
461           // a = b = c, alpha = beta = gamma = 90
462           g[index] = finiteDifference3(x, index, index + 1, index + 2, eps);
463           index++;
464           g[index] = g[index - 1];
465           index++;
466           g[index] = g[index - 2];
467           index++;
468           g[index] = 0.0;
469           index++;
470           g[index] = 0.0;
471           index++;
472           g[index] = 0.0;
473         }
474       }
475 
476     // Scale finite-difference partial derivatives of lattice parameters.
477     // Coordinate scaling has already been applied.
478     if (scaling != null) {
479       index = 3 * nActive;
480       for (int i = 0; i < 6; i++) {
481         g[index] /= scaling[index];
482         index++;
483       }
484     }
485   }
486 
487   /**
488    * Calculate finite-difference derivative for any parameter.
489    *
490    * @param x     Coordinates and unit cell parameters.
491    * @param index Parameter index.
492    * @param eps   Step size.
493    * @return Finite-difference derivative.
494    */
495   private double finiteDifference(double[] x, int index, double eps) {
496     double scale = 1.0;
497     if (scaling != null) {
498       scale = scaling[index];
499     }
500     double x1 = x[index];
501     final double param = x1 / scale;
502 
503     x[index] = (param + eps / 2.0) * scale;
504     double ePlus = energy(x);
505     x[index] = (param - eps / 2.0) * scale;
506     double eMinus = energy(x);
507 
508     x[index] = x1;
509 
510     return (ePlus - eMinus) / eps;
511   }
512 
513   /**
514    * @param x      Coordinates and unit cell parameters.
515    * @param index1 Parameter index 1.
516    * @param index2 Parameter index 2.
517    * @param eps    Step size.
518    * @return Finite-difference derivative.
519    */
520   private double finiteDifference2(double[] x, int index1, int index2, double eps) {
521     double scale1 = 1.0;
522     double scale2 = 1.0;
523 
524     if (scaling != null) {
525       scale1 = scaling[index1];
526       scale2 = scaling[index2];
527     }
528 
529     final double x1 = x[index1];
530     final double x2 = x[index2];
531     final double param1 = x1 / scale1;
532     final double param2 = x2 / scale2;
533     x[index1] = (param1 + eps / 2.0) * scale1;
534     x[index2] = (param2 + eps / 2.0) * scale2;
535     double ePlus = energy(x);
536     x[index1] = (param1 - eps / 2.0) * scale1;
537     x[index2] = (param2 - eps / 2.0) * scale2;
538     double eMinus = energy(x);
539 
540     x[index1] = x1;
541     x[index2] = x2;
542 
543     return (ePlus - eMinus) / eps;
544   }
545 
546   /**
547    * @param x      Coordinates and unit cell parameters.
548    * @param index1 Parameter index 1.
549    * @param index2 Parameter index 2.
550    * @param index3 Parameter index 3.
551    * @param eps    Step size.
552    * @return finite-difference derivative.
553    */
554   private double finiteDifference3(double[] x, int index1, int index2, int index3, double eps) {
555     double scale1 = 1.0;
556     double scale2 = 1.0;
557     double scale3 = 1.0;
558     if (scaling != null) {
559       scale1 = scaling[index1];
560       scale2 = scaling[index2];
561       scale3 = scaling[index3];
562     }
563 
564     final double x1 = x[index1];
565     final double x2 = x[index2];
566     final double x3 = x[index3];
567 
568     final double param1 = x1 / scale1;
569     final double param2 = x2 / scale2;
570     final double param3 = x3 / scale3;
571     x[index1] = (param1 + eps / 2.0) * scale1;
572     x[index2] = (param2 + eps / 2.0) * scale2;
573     x[index3] = (param3 + eps / 2.0) * scale3;
574     final double ePlus = energy(x);
575     x[index1] = (param1 - eps / 2.0) * scale1;
576     x[index2] = (param2 - eps / 2.0) * scale2;
577     x[index3] = (param3 - eps / 2.0) * scale3;
578     final double eMinus = energy(x);
579 
580     x[index1] = x1;
581     x[index2] = x2;
582     x[index3] = x3;
583 
584     return (ePlus - eMinus) / eps;
585   }
586 
587   /**
588    * Apply scaling for the optimizer if applicable.
589    *
590    * @param x Coordinates and unit cell parameters.
591    * @param g Atomic coordinate gradient.
592    */
593   private void packGradient(double[] x, double[] g) {
594     // Scale fractional coordinates and gradient.
595     if (scaling != null) {
596       int len = x.length;
597       for (int i = 0; i < len; i++) {
598         if (fractionalMode == FractionalMode.OFF) {
599           g[i] /= scaling[i];
600         } else {
601           // If we're maintaining fractional coordinates,
602           // the atomic coordinate derivatives can be zero.
603           g[i] = 0.0;
604         }
605         x[i] *= scaling[i];
606       }
607     }
608   }
609 
610   /**
611    * Sets atomic coordinates and lattice parameters.
612    *
613    * @param x First 3*nActive parameters are coordinates, next 6 are x parameters.
614    */
615   @Override
616   public void setCoordinates(double[] x) {
617     assert (x != null);
618 
619     // Before applying new lattice parameters, store factional coordinates.
620     if (fractionalMode != FractionalMode.OFF) {
621       molecularAssembly.computeFractionalCoordinates();
622     }
623 
624     int index = nActive * 3;
625     double a = x[index];
626     double b = x[index + 1];
627     double c = x[index + 2];
628     double alpha = x[index + 3];
629     double beta = x[index + 4];
630     double gamma = x[index + 5];
631 
632     SpaceGroup spaceGroup = crystal.spaceGroup;
633 
634     // Enforce the lattice system.
635     switch (spaceGroup.latticeSystem) {
636       case TRICLINIC_LATTICE -> {
637       }
638       case MONOCLINIC_LATTICE -> {
639         // alpha = gamma = 90
640         alpha = 90.0;
641         gamma = 90.0;
642       }
643       case ORTHORHOMBIC_LATTICE -> {
644         // alpha = beta = gamma = 90
645         alpha = 90.0;
646         beta = 90.0;
647         gamma = 90.0;
648       }
649       case TETRAGONAL_LATTICE -> {
650         // a = b, alpha = beta = gamma = 90
651         double ab = 0.5 * (a + b);
652         a = ab;
653         b = ab;
654         alpha = 90.0;
655         beta = 90.0;
656         gamma = 90.0;
657       }
658       case RHOMBOHEDRAL_LATTICE -> {
659         // a = b = c, alpha = beta = gamma.
660         double abc = (a + b + c) / 3.0;
661         a = abc;
662         b = abc;
663         c = abc;
664         double angles = (alpha + beta + gamma) / 3.0;
665         alpha = angles;
666         beta = angles;
667         gamma = angles;
668       }
669       case HEXAGONAL_LATTICE -> {
670         // a = b, alpha = beta = 90 && gamma = 120
671         double ab = 0.5 * (a + b);
672         a = ab;
673         b = ab;
674         alpha = 90.0;
675         beta = 90.0;
676         gamma = 120.0;
677       }
678       case CUBIC_LATTICE -> {
679         // a = b = c, alpha = beta = gamma = 90
680         double abc = (a + b + c) / 3.0;
681         a = abc;
682         b = abc;
683         c = abc;
684         alpha = 90.0;
685         beta = 90.0;
686         gamma = 90.0;
687       }
688     }
689 
690     crystal.changeUnitCellParameters(a, b, c, alpha, beta, gamma);
691 
692     forceFieldEnergy.setCrystal(crystal);
693 
694     // Use the atomic coordinates from the optimizer.
695     if (fractionalMode == FractionalMode.OFF) {
696       index = 0;
697       for (int i = 0; i < nActive; i++) {
698         Atom atom = activeAtoms[i];
699         double xx = x[index];
700         double yy = x[index + 1];
701         double zz = x[index + 2];
702         xyz[index] = xx;
703         xyz[index + 1] = yy;
704         xyz[index + 2] = zz;
705         index += 3;
706         atom.moveTo(xx, yy, zz);
707       }
708     } else {
709       // Maintain fractional coordinates.
710       molecularAssembly.moveToFractionalCoordinates();
711       index = 0;
712       for (int i = 0; i < nActive; i++) {
713         Atom atom = activeAtoms[i];
714         double xx = atom.getX();
715         double yy = atom.getY();
716         double zz = atom.getZ();
717         xyz[index] = xx;
718         xyz[index + 1] = yy;
719         xyz[index + 2] = zz;
720         index += 3;
721       }
722     }
723   }
724 
725 }