1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
54
55
56
57
58
59 public class XtalEnergy implements Potential {
60
61 private static final Logger logger = Logger.getLogger(XtalEnergy.class.getName());
62
63
64
65
66 private final MolecularAssembly molecularAssembly;
67
68
69
70 private final ForceFieldEnergy forceFieldEnergy;
71
72
73
74 private final Atom[] activeAtoms;
75
76
77
78 private final int nActive;
79
80
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
97
98
99
100
101 public XtalEnergy(ForceFieldEnergy forceFieldEnergy, MolecularAssembly molecularAssembly) {
102 this(forceFieldEnergy, molecularAssembly, false);
103 }
104
105
106
107
108
109
110
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
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
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
166
167 @Override
168 public boolean destroy() {
169 return forceFieldEnergy.destroy();
170 }
171
172
173
174
175 @Override
176 public double energy(double[] x) {
177
178
179 unscaleCoordinates(x);
180
181
182 setCoordinates(x);
183
184
185 totalEnergy = forceFieldEnergy.energy(false, false);
186
187
188 scaleCoordinates(x);
189
190 return totalEnergy;
191 }
192
193
194
195
196 @Override
197 public double energyAndGradient(double[] x, double[] g) {
198
199 unscaleCoordinates(x);
200
201
202 setCoordinates(x);
203
204 if (latticeOnly) {
205
206 totalEnergy = forceFieldEnergy.energy(false, false);
207 } else {
208
209 totalEnergy = forceFieldEnergy.energyAndGradient(xyz, gr);
210
211 }
212
213
214 packGradient(x, g);
215
216
217 unitCellParameterDerivatives(x, g);
218
219 return totalEnergy;
220 }
221
222
223
224
225 @Override
226 public double[] getAcceleration(double[] acceleration) {
227 throw new UnsupportedOperationException("Not supported yet.");
228 }
229
230
231
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
265
266 @Override
267 public STATE getEnergyTermState() {
268 return forceFieldEnergy.getEnergyTermState();
269 }
270
271
272
273
274 @Override
275 public void setEnergyTermState(STATE state) {
276 forceFieldEnergy.setEnergyTermState(state);
277 }
278
279
280
281
282 @Override
283 public double[] getMass() {
284 return mass;
285 }
286
287
288
289
290 @Override
291 public int getNumberOfVariables() {
292 return nParams;
293 }
294
295
296
297
298 @Override
299 public double[] getPreviousAcceleration(double[] previousAcceleration) {
300 throw new UnsupportedOperationException("Not supported yet.");
301 }
302
303
304
305
306 @Override
307 public double[] getScaling() {
308 return scaling;
309 }
310
311
312
313
314 @Override
315 public void setScaling(double[] scaling) {
316 this.scaling = scaling;
317 }
318
319
320
321
322 @Override
323 public double getTotalEnergy() {
324 return totalEnergy;
325 }
326
327
328
329
330 @Override
331 public VARIABLE_TYPE[] getVariableTypes() {
332 return type;
333 }
334
335
336
337
338 @Override
339 public double[] getVelocity(double[] velocity) {
340 throw new UnsupportedOperationException("Not supported yet.");
341 }
342
343
344
345
346 @Override
347 public void setAcceleration(double[] acceleration) {
348 throw new UnsupportedOperationException("Not supported yet.");
349 }
350
351
352
353
354
355
356 public void setFractionalCoordinateMode(FractionalMode fractionalMode) {
357 this.fractionalMode = fractionalMode;
358 molecularAssembly.setFractionalMode(fractionalMode);
359 }
360
361
362
363
364 @Override
365 public void setPreviousAcceleration(double[] previousAcceleration) {
366 throw new UnsupportedOperationException("Not supported yet.");
367 }
368
369
370
371
372 @Override
373 public void setVelocity(double[] velocity) {
374 throw new UnsupportedOperationException("Not supported yet.");
375 }
376
377
378
379
380
381
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
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
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,
431 HEXAGONAL_LATTICE
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
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
460 case CUBIC_LATTICE -> {
461
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
477
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
489
490
491
492
493
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
515
516
517
518
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
548
549
550
551
552
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
589
590
591
592
593 private void packGradient(double[] x, double[] g) {
594
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
602
603 g[i] = 0.0;
604 }
605 x[i] *= scaling[i];
606 }
607 }
608 }
609
610
611
612
613
614
615 @Override
616 public void setCoordinates(double[] x) {
617 assert (x != null);
618
619
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
635 switch (spaceGroup.latticeSystem) {
636 case TRICLINIC_LATTICE -> {
637 }
638 case MONOCLINIC_LATTICE -> {
639
640 alpha = 90.0;
641 gamma = 90.0;
642 }
643 case ORTHORHOMBIC_LATTICE -> {
644
645 alpha = 90.0;
646 beta = 90.0;
647 gamma = 90.0;
648 }
649 case TETRAGONAL_LATTICE -> {
650
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
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
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
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
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
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 }