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.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
55
56
57
58
59
60 public class XtalEnergy implements Potential {
61
62
63
64
65 private final MolecularAssembly molecularAssembly;
66
67
68
69 private final ForceFieldEnergy forceFieldEnergy;
70
71
72
73 private final Atom[] activeAtoms;
74
75
76
77 private final int nActive;
78
79
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
96
97
98
99
100 public XtalEnergy(ForceFieldEnergy forceFieldEnergy, MolecularAssembly molecularAssembly) {
101 this(forceFieldEnergy, molecularAssembly, false);
102 }
103
104
105
106
107
108
109
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
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
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
162
163 @Override
164 public boolean destroy() {
165 return forceFieldEnergy.destroy();
166 }
167
168
169
170
171 @Override
172 public double energy(double[] x) {
173
174
175 unscaleCoordinates(x);
176
177
178 setCoordinates(x);
179
180
181 totalEnergy = forceFieldEnergy.energy(false, false);
182
183
184 scaleCoordinates(x);
185
186 return totalEnergy;
187 }
188
189
190
191
192 @Override
193 public double energyAndGradient(double[] x, double[] g) {
194
195 unscaleCoordinates(x);
196
197
198 setCoordinates(x);
199
200 if (latticeOnly) {
201
202 totalEnergy = forceFieldEnergy.energy(false, false);
203 } else {
204
205 totalEnergy = forceFieldEnergy.energyAndGradient(xyz, gr);
206
207 }
208
209
210 packGradient(x, g);
211
212
213 unitCellParameterDerivatives(x, g);
214
215 return totalEnergy;
216 }
217
218
219
220
221 @Override
222 public double[] getAcceleration(double[] acceleration) {
223 throw new UnsupportedOperationException("Not supported yet.");
224 }
225
226
227
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
261
262 @Override
263 public STATE getEnergyTermState() {
264 return forceFieldEnergy.getEnergyTermState();
265 }
266
267
268
269
270 @Override
271 public void setEnergyTermState(STATE state) {
272 forceFieldEnergy.setEnergyTermState(state);
273 }
274
275
276
277
278 @Override
279 public double[] getMass() {
280 return mass;
281 }
282
283
284
285
286 @Override
287 public int getNumberOfVariables() {
288 return nParams;
289 }
290
291
292
293
294 @Override
295 public double[] getPreviousAcceleration(double[] previousAcceleration) {
296 throw new UnsupportedOperationException("Not supported yet.");
297 }
298
299
300
301
302 @Override
303 public double[] getScaling() {
304 return scaling;
305 }
306
307
308
309
310 @Override
311 public void setScaling(double[] scaling) {
312 this.scaling = scaling;
313 }
314
315
316
317
318 @Override
319 public double getTotalEnergy() {
320 return totalEnergy;
321 }
322
323
324
325
326 @Override
327 public VARIABLE_TYPE[] getVariableTypes() {
328 return type;
329 }
330
331
332
333
334 @Override
335 public double[] getVelocity(double[] velocity) {
336 throw new UnsupportedOperationException("Not supported yet.");
337 }
338
339
340
341
342 @Override
343 public void setAcceleration(double[] acceleration) {
344 throw new UnsupportedOperationException("Not supported yet.");
345 }
346
347
348
349
350
351
352 public void setFractionalCoordinateMode(FractionalMode fractionalMode) {
353 this.fractionalMode = fractionalMode;
354 molecularAssembly.setFractionalMode(fractionalMode);
355 }
356
357
358
359
360 @Override
361 public void setPreviousAcceleration(double[] previousAcceleration) {
362 throw new UnsupportedOperationException("Not supported yet.");
363 }
364
365
366
367
368 @Override
369 public void setVelocity(double[] velocity) {
370 throw new UnsupportedOperationException("Not supported yet.");
371 }
372
373
374
375
376
377
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
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
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,
427 HEXAGONAL_LATTICE
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
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
456 case CUBIC_LATTICE -> {
457
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
473
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
485
486
487
488
489
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
511
512
513
514
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
544
545
546
547
548
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
585
586
587
588
589 private void packGradient(double[] x, double[] g) {
590
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
598
599 g[i] = 0.0;
600 }
601 x[i] *= scaling[i];
602 }
603 }
604 }
605
606
607
608
609
610
611 private void setCoordinates(double[] x) {
612 assert (x != null);
613
614
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
630 switch (spaceGroup.latticeSystem) {
631 case TRICLINIC_LATTICE -> {
632 }
633 case MONOCLINIC_LATTICE -> {
634
635 alpha = 90.0;
636 gamma = 90.0;
637 }
638 case ORTHORHOMBIC_LATTICE -> {
639
640 alpha = 90.0;
641 beta = 90.0;
642 gamma = 90.0;
643 }
644 case TETRAGONAL_LATTICE -> {
645
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
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
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
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
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
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 }