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