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.crystal;
39
40 import ffx.utilities.FFXProperty;
41 import org.apache.commons.configuration2.CompositeConfiguration;
42 import org.apache.commons.math3.linear.Array2DRowRealMatrix;
43 import org.apache.commons.math3.linear.LUDecomposition;
44 import org.apache.commons.math3.linear.RealMatrix;
45
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.logging.Level;
50 import java.util.logging.Logger;
51
52 import static ffx.crystal.SpaceGroupDefinitions.spaceGroupFactory;
53 import static ffx.numerics.math.DoubleMath.dot;
54 import static ffx.numerics.math.DoubleMath.length;
55 import static ffx.numerics.math.MatrixMath.mat3Mat3;
56 import static ffx.numerics.math.MatrixMath.mat3SymVec6;
57 import static ffx.numerics.math.MatrixMath.transpose3;
58 import static ffx.numerics.math.ScalarMath.mod;
59 import static ffx.utilities.Constants.AVOGADRO;
60 import static ffx.utilities.PropertyGroup.UnitCellAndSpaceGroup;
61 import static ffx.utilities.StringUtils.padRight;
62 import static java.lang.String.format;
63 import static org.apache.commons.math3.util.FastMath.abs;
64 import static org.apache.commons.math3.util.FastMath.acos;
65 import static org.apache.commons.math3.util.FastMath.cbrt;
66 import static org.apache.commons.math3.util.FastMath.cos;
67 import static org.apache.commons.math3.util.FastMath.floor;
68 import static org.apache.commons.math3.util.FastMath.min;
69 import static org.apache.commons.math3.util.FastMath.random;
70 import static org.apache.commons.math3.util.FastMath.signum;
71 import static org.apache.commons.math3.util.FastMath.sin;
72 import static org.apache.commons.math3.util.FastMath.sqrt;
73 import static org.apache.commons.math3.util.FastMath.toDegrees;
74 import static org.apache.commons.math3.util.FastMath.toRadians;
75
76
77
78
79
80
81
82
83
84
85 public class Crystal {
86
87 private static final Logger logger = Logger.getLogger(Crystal.class.getName());
88
89
90
91
92 @FFXProperty(name = "SpaceGroup", propertyGroup = UnitCellAndSpaceGroup, clazz = SpaceGroup.class, defaultValue = "P1",
93 description = """
94 This property defines the crystal space group used during structural manipulations and force field calculations.
95 """)
96 public final SpaceGroup spaceGroup;
97
98
99
100
101 @FFXProperty(name = "a-axis", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "None",
102 description = """
103 Sets the value of the a-axis length for a crystal unit cell, or, equivalently,
104 the X-axis length for a periodic box (Angstroms).
105 """)
106 public double a;
107
108
109
110
111 @FFXProperty(name = "b-axis", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "A-Axis",
112 description = """
113 Sets the value of the b-axis length for a crystal unit cell, or, equivalently,
114 the Y-axis length for a periodic box (Angstroms).
115 """)
116 public double b;
117
118
119
120
121 @FFXProperty(name = "c-axis", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "A-Axis",
122 description = """
123 Sets the value of the c-axis length for a crystal unit cell, or, equivalently,
124 the Z-axis length for a periodic box (Angstroms).
125 """)
126 public double c;
127
128
129
130
131 @FFXProperty(name = "alpha", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "90.0",
132 description = """
133 Sets the value of the α-angle of a crystal unit cell, i.e.,
134 the angle between the b-axis and c-axis of a unit cell, or, equivalently,
135 the angle between the Y-axis and Z-axis of a periodic box.
136 """)
137 public double alpha;
138
139
140
141
142 @FFXProperty(name = "beta", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "Alpha",
143 description = """
144 Sets the value of the β-angle of a crystal unit cell, i.e.,
145 the angle between the a-axis and c-axis of a unit cell, or, equivalently,
146 the angle between the X-axis and Z-axis of a periodic box.
147 """)
148 public double beta;
149
150
151
152
153 @FFXProperty(name = "gamma", propertyGroup = UnitCellAndSpaceGroup, defaultValue = "Alpha",
154 description = """
155 Sets the value of the γ-angle of a crystal unit cell, i.e.,
156 the angle between the a-axis and b-axis of a unit cell, or, equivalently,
157 the angle between the X-axis and Y-axis of a periodic box.
158 """)
159 public double gamma;
160
161
162
163
164 private static final int XX = 0;
165
166
167
168 private static final int YY = 1;
169
170
171
172 private static final int ZZ = 2;
173
174
175
176
177
178
179
180 public final double[][] Ai = new double[3][3];
181
182
183
184 public final double[][] G = new double[3][3];
185
186
187
188 private final CrystalSystem crystalSystem;
189
190
191
192 private final LatticeSystem latticeSystem;
193
194
195
196 public double volume;
197
198
199
200
201
202
203 public double[][] A;
204
205
206
207 public double A00, A10, A20;
208
209
210
211 public double A11, A21;
212
213
214
215 public double A22;
216
217
218
219
220 public double interfacialRadiusA;
221
222
223
224 public double interfacialRadiusB;
225
226
227
228 public double interfacialRadiusC;
229
230
231
232 public int[] scaleB = new int[6];
233
234
235
236 public int scaleN;
237
238
239
240 public double Ai00;
241
242
243
244 public double Ai10, Ai11;
245
246
247
248 public double Ai20, Ai21, Ai22;
249
250
251
252 public double dVdA;
253
254
255
256 public double dVdB;
257
258
259
260 public double dVdC;
261
262
263
264 public double dVdAlpha;
265
266
267
268 public double dVdBeta;
269
270
271
272 public double dVdGamma;
273
274
275
276
277 boolean checkRestrictions = true;
278
279
280
281
282 private double specialPositionCutoff = 0.3;
283
284
285
286 private List<SymOp> symOpsCartesian;
287
288
289
290 private double[][] Gstar;
291
292
293
294 private double specialPositionCutoff2 = specialPositionCutoff * specialPositionCutoff;
295
296
297
298 private boolean aperiodic;
299
300
301
302
303
304
305
306
307
308
309
310
311
312 public Crystal(double a, double b, double c, double alpha, double beta, double gamma, int sgNumber) {
313 this(a, b, c, alpha, beta, gamma, spaceGroupFactory(sgNumber).pdbName);
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327
328 public Crystal(double a, double b, double c, double alpha, double beta, double gamma, String sg) {
329
330 SpaceGroup tempSG = spaceGroupFactory(sg);
331 LatticeSystem tempLS = tempSG.latticeSystem;
332 this.a = a;
333 this.b = b;
334 this.c = c;
335 this.alpha = alpha;
336 this.beta = beta;
337 this.gamma = gamma;
338 aperiodic = false;
339 if (!tempLS.validParameters(a, b, c, alpha, beta, gamma)) {
340
341 StringBuilder sb = new StringBuilder(format(
342 " The %s lattice parameters do not satisfy the %s lattice system restrictions.\n",
343 tempSG.pdbName, tempLS));
344 sb.append(format(" A-axis: %18.15e\n", a));
345 sb.append(format(" B-axis: %18.15e\n", b));
346 sb.append(format(" C-axis: %18.15e\n", c));
347 sb.append(format(" Alpha: %18.15e\n", alpha));
348 sb.append(format(" Beta: %18.15e\n", beta));
349 sb.append(format(" Gamma: %18.15e\n", gamma));
350 logger.info(sb.toString());
351 sb = new StringBuilder();
352 if (tempLS == LatticeSystem.HEXAGONAL_LATTICE
353 || tempLS == LatticeSystem.RHOMBOHEDRAL_LATTICE) {
354
355 Crystal convertedCrystal = SpaceGroupConversions.hrConversion(a, b, c, alpha, beta, gamma,
356 tempSG);
357 this.a = convertedCrystal.a;
358 this.b = convertedCrystal.b;
359 this.c = convertedCrystal.c;
360 this.alpha = convertedCrystal.alpha;
361 this.beta = convertedCrystal.beta;
362 this.gamma = convertedCrystal.gamma;
363 spaceGroup = convertedCrystal.spaceGroup;
364 crystalSystem = spaceGroup.crystalSystem;
365 latticeSystem = spaceGroup.latticeSystem;
366 sb.append(" Converted ").append(tempSG.pdbName).append(" to ").append(spaceGroup.pdbName);
367 if (!latticeSystem.validParameters(this.a, this.b, this.c, this.alpha, this.beta,
368 this.gamma)) {
369 sb.append(format(
370 " The %s lattice parameters do not satisfy the %s lattice system restrictions.\n",
371 spaceGroup.pdbName, latticeSystem));
372 sb.append(format(" A-axis: %18.15e\n", this.a));
373 sb.append(format(" B-axis: %18.15e\n", this.b));
374 sb.append(format(" C-axis: %18.15e\n", this.c));
375 sb.append(format(" Alpha: %18.15e\n", this.alpha));
376 sb.append(format(" Beta: %18.15e\n", this.beta));
377 sb.append(format(" Gamma: %18.15e\n", this.gamma));
378 logger.severe(sb.toString());
379 } else {
380
381 logger.info(sb.toString());
382 }
383 } else {
384
385 this.a = a;
386 this.b = b;
387 this.c = c;
388 this.alpha = alpha;
389 this.beta = beta;
390 this.gamma = gamma;
391 spaceGroup = tempSG;
392 crystalSystem = spaceGroup.crystalSystem;
393 latticeSystem = spaceGroup.latticeSystem;
394 logger.severe(sb.toString());
395 }
396 } else {
397
398 this.a = a;
399 this.b = b;
400 this.c = c;
401 this.alpha = alpha;
402 this.beta = beta;
403 this.gamma = gamma;
404 spaceGroup = tempSG;
405 crystalSystem = spaceGroup.crystalSystem;
406 latticeSystem = spaceGroup.latticeSystem;
407 }
408
409 for (int i = 0; i < 6; i++) {
410 scaleB[i] = -1;
411 }
412
413 SymOp symop;
414 double[][] rot;
415 int index = 0;
416 switch (crystalSystem) {
417 case TRICLINIC:
418 for (int i = 0; i < 6; i++) {
419 scaleB[i] = index++;
420 }
421 break;
422 case MONOCLINIC:
423 scaleB[0] = index++;
424 scaleB[1] = index++;
425 scaleB[2] = index++;
426
427 symop = spaceGroup.symOps.get(1);
428 rot = symop.rot;
429 if (rot[0][0] > 0) {
430 scaleB[5] = index++;
431 } else if (rot[1][1] > 0) {
432 scaleB[4] = index++;
433 } else {
434 scaleB[3] = index++;
435 }
436 break;
437 case ORTHORHOMBIC:
438 scaleB[0] = index++;
439 scaleB[1] = index++;
440 scaleB[2] = index++;
441 break;
442 case TETRAGONAL:
443 scaleB[0] = index++;
444 scaleB[1] = scaleB[0];
445 scaleB[2] = index++;
446 break;
447 case TRIGONAL:
448 case HEXAGONAL:
449 boolean hexagonal = false;
450 for (int i = 1; i < spaceGroup.symOps.size(); i++) {
451 symop = spaceGroup.symOps.get(i);
452 rot = symop.rot;
453 index = 0;
454 if ((rot[1][1] * rot[1][2] == -1)
455 || (rot[2][1] * rot[2][2] == -1)
456 || (rot[1][1] * rot[1][2] == 1)
457 || (rot[2][1] * rot[2][2] == 1)) {
458 scaleB[0] = index++;
459 scaleB[1] = index++;
460 scaleB[2] = scaleB[1];
461 hexagonal = true;
462 } else if ((rot[0][0] * rot[0][2] == -1)
463 || (rot[2][0] * rot[2][2] == -1)
464 || (rot[0][0] * rot[0][2] == 1)
465 || (rot[2][0] * rot[2][2] == 1)) {
466 scaleB[0] = index++;
467 scaleB[1] = index++;
468 scaleB[2] = scaleB[0];
469 hexagonal = true;
470 } else if ((rot[0][0] * rot[0][1] == -1)
471 || (rot[1][0] * rot[1][1] == -1)
472 || (rot[0][0] * rot[0][1] == 1)
473 || (rot[1][0] * rot[1][1] == 1)) {
474 scaleB[0] = index++;
475 scaleB[1] = scaleB[0];
476 scaleB[2] = index++;
477 hexagonal = true;
478 }
479 if (hexagonal) {
480 break;
481 }
482 }
483 if (!hexagonal) {
484
485 scaleB[3] = index++;
486 scaleB[4] = scaleB[3];
487 scaleB[5] = scaleB[3];
488 }
489 break;
490 case CUBIC:
491 break;
492 }
493 scaleN = index;
494
495 updateCrystal();
496 }
497
498
499
500
501
502
503
504
505 public static Crystal checkProperties(CompositeConfiguration properties) {
506 double a = properties.getDouble("a-axis", -1.0);
507 double b = properties.getDouble("b-axis", -1.0);
508 double c = properties.getDouble("c-axis", -1.0);
509 double alpha = properties.getDouble("alpha", -1.0);
510 double beta = properties.getDouble("beta", -1.0);
511 double gamma = properties.getDouble("gamma", -1.0);
512 String sg = properties.getString("spacegroup", null);
513
514 sg = SpaceGroupInfo.pdb2ShortName(sg);
515
516 if (a < 0.0 || b < 0.0 || c < 0.0 || alpha < 0.0 || beta < 0.0 || gamma < 0.0 || sg == null) {
517 return null;
518 }
519
520
521 SpaceGroup spaceGroup = spaceGroupFactory(sg);
522 if (spaceGroup == null) {
523 sg = sg.replaceAll(" ", "");
524 spaceGroup = spaceGroupFactory(sg);
525 if (spaceGroup == null) {
526 return null;
527 }
528 }
529
530 return new Crystal(a, b, c, alpha, beta, gamma, sg);
531 }
532
533
534
535
536
537
538
539 public double invressq(HKL hkl) {
540 return hkl.quadForm(Gstar);
541 }
542
543
544
545
546
547
548
549 public double res(HKL hkl) {
550 return 1.0 / sqrt(hkl.quadForm(Gstar));
551 }
552
553
554
555
556
557
558 public boolean aperiodic() {
559 return aperiodic;
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578 public void applySymOp(int n, double[] x, double[] y, double[] z,
579 double[] mateX, double[] mateY, double[] mateZ, SymOp symOp) {
580 if (x == null || y == null || z == null) {
581 throw new IllegalArgumentException("The input arrays x, y and z must not be null.");
582 }
583 if (x.length < n || y.length < n || z.length < n) {
584 throw new IllegalArgumentException("The input arrays x, y and z must be of length n: " + n);
585 }
586 if (mateX == null || mateY == null || mateZ == null) {
587 throw new IllegalArgumentException("The output arrays mateX, mateY and mateZ must not be null.");
588 }
589 if (mateX.length < n || mateY.length < n || mateZ.length < n) {
590 throw new IllegalArgumentException("The output arrays mateX, mateY and mateZ must be of length n: " + n);
591 }
592 final double[][] rot = symOp.rot;
593 final double[] trans = symOp.tr;
594
595 final double rot00 = rot[0][0];
596 final double rot10 = rot[1][0];
597 final double rot20 = rot[2][0];
598 final double rot01 = rot[0][1];
599 final double rot11 = rot[1][1];
600 final double rot21 = rot[2][1];
601 final double rot02 = rot[0][2];
602 final double rot12 = rot[1][2];
603 final double rot22 = rot[2][2];
604 final double t0 = trans[0];
605 final double t1 = trans[1];
606 final double t2 = trans[2];
607 for (int i = 0; i < n; i++) {
608 double xc = x[i];
609 double yc = y[i];
610 double zc = z[i];
611
612 double xi = xc * A00 + yc * A10 + zc * A20;
613 double yi = yc * A11 + zc * A21;
614 double zi = zc * A22;
615
616 double fx = rot00 * xi + rot01 * yi + rot02 * zi + t0;
617 double fy = rot10 * xi + rot11 * yi + rot12 * zi + t1;
618 double fz = rot20 * xi + rot21 * yi + rot22 * zi + t2;
619
620 mateX[i] = fx * Ai00 + fy * Ai10 + fz * Ai20;
621 mateY[i] = fy * Ai11 + fz * Ai21;
622 mateZ[i] = fz * Ai22;
623 }
624 }
625
626
627
628
629
630
631
632
633 public void applySymOp(double[] xyz, double[] mate, SymOp symOp) {
634 assert (xyz.length % 3 == 0);
635 assert (xyz.length == mate.length);
636
637 var rot = symOp.rot;
638 var r00 = rot[0][0];
639 var r01 = rot[0][1];
640 var r02 = rot[0][2];
641 var r10 = rot[1][0];
642 var r11 = rot[1][1];
643 var r12 = rot[1][2];
644 var r20 = rot[2][0];
645 var r21 = rot[2][1];
646 var r22 = rot[2][2];
647
648 var trans = symOp.tr;
649 var t0 = trans[0];
650 var t1 = trans[1];
651 var t2 = trans[2];
652
653 int len = xyz.length / 3;
654 for (int i = 0; i < len; i++) {
655 int index = i * 3;
656 var xc = xyz[index + XX];
657 var yc = xyz[index + YY];
658 var zc = xyz[index + ZZ];
659
660 var xi = xc * A00 + yc * A10 + zc * A20;
661 var yi = yc * A11 + zc * A21;
662 var zi = zc * A22;
663
664 var fx = r00 * xi + r01 * yi + r02 * zi + t0;
665 var fy = r10 * xi + r11 * yi + r12 * zi + t1;
666 var fz = r20 * xi + r21 * yi + r22 * zi + t2;
667
668 mate[index + XX] = fx * Ai00 + fy * Ai10 + fz * Ai20;
669 mate[index + YY] = fy * Ai11 + fz * Ai21;
670 mate[index + ZZ] = fz * Ai22;
671 }
672 }
673
674
675
676
677
678
679
680
681 public void applySymRot(double[] xyz, double[] mate, SymOp symOp) {
682 double[][] rot = symOp.rot;
683
684 double xc = xyz[0];
685 double yc = xyz[1];
686 double zc = xyz[2];
687
688 double xi = xc * A00 + yc * A10 + zc * A20;
689 double yi = yc * A11 + zc * A21;
690 double zi = zc * A22;
691
692
693 double fx = rot[0][0] * xi + rot[0][1] * yi + rot[0][2] * zi;
694 double fy = rot[1][0] * xi + rot[1][1] * yi + rot[1][2] * zi;
695 double fz = rot[2][0] * xi + rot[2][1] * yi + rot[2][2] * zi;
696
697
698 mate[0] = fx * Ai00 + fy * Ai10 + fz * Ai20;
699 mate[1] = fy * Ai11 + fz * Ai21;
700 mate[2] = fz * Ai22;
701 }
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720 public void applyTransSymRot(
721 int n, double[] x, double[] y, double[] z,
722 double[] mateX, double[] mateY, double[] mateZ, SymOp symOp, double[][] rotmat) {
723
724 if (x == null || y == null || z == null) {
725 throw new IllegalArgumentException("The input arrays x, y and z must not be null.");
726 }
727 if (x.length < n || y.length < n || z.length < n) {
728 throw new IllegalArgumentException("The input arrays x, y and z must be of length n: " + n);
729 }
730 if (mateX == null || mateY == null || mateZ == null) {
731 throw new IllegalArgumentException("The output arrays mateX, mateY and mateZ must not be null.");
732 }
733 if (mateX.length < n || mateY.length < n || mateZ.length < n) {
734 throw new IllegalArgumentException("The output arrays mateX, mateY and mateZ must be of length n: " + n);
735 }
736
737
738 getTransformationOperator(symOp, rotmat);
739
740 for (int i = 0; i < n; i++) {
741
742 double xc = x[i];
743 double yc = y[i];
744 double zc = z[i];
745 mateX[i] = xc * rotmat[0][0] + yc * rotmat[1][0] + zc * rotmat[2][0];
746 mateY[i] = xc * rotmat[0][1] + yc * rotmat[1][1] + zc * rotmat[2][1];
747 mateZ[i] = xc * rotmat[0][2] + yc * rotmat[1][2] + zc * rotmat[2][2];
748 }
749
750 }
751
752
753
754
755
756
757
758 public void averageTensor(double[][] m, double[][] r) {
759 int n = spaceGroup.symOps.size();
760 for (int i = 0; i < n; i++) {
761 SymOp symop = spaceGroup.symOps.get(i);
762 double[][] rot = symop.rot;
763 double[][] rt = transpose3(rot);
764 double[][] rmrt = mat3Mat3(mat3Mat3(rot, m), rt);
765 for (int j = 0; j < 3; j++) {
766 for (int k = 0; k < 3; k++) {
767 r[j][k] += rmrt[j][k];
768 }
769 }
770 }
771 for (int j = 0; j < 3; j++) {
772 for (int k = 0; k < 3; k++) {
773 r[j][k] /= 6.0;
774 }
775 }
776 }
777
778
779
780
781
782
783
784 public void averageTensor(double[] v, double[][] r) {
785 int n = spaceGroup.symOps.size();
786 for (int i = 0; i < n; i++) {
787 SymOp symop = spaceGroup.symOps.get(i);
788 double[][] rot = symop.rot;
789 double[][] rt = transpose3(rot);
790 double[][] rmrt = mat3Mat3(mat3SymVec6(rot, v), rt);
791 for (int j = 0; j < 3; j++) {
792 for (int k = 0; k < 3; k++) {
793 r[j][k] += rmrt[j][k];
794 }
795 }
796 }
797 for (int j = 0; j < 3; j++) {
798 for (int k = 0; k < 3; k++) {
799 r[j][k] /= 6.0;
800 }
801 }
802 }
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817 public boolean changeUnitCellParameters(
818 double a, double b, double c, double alpha, double beta, double gamma) {
819 if (checkRestrictions) {
820 if (!latticeSystem.validParameters(a, b, c, alpha, beta, gamma)) {
821 if (logger.isLoggable(Level.FINE)) {
822 StringBuilder sb = new StringBuilder(
823 " The proposed lattice parameters for " + spaceGroup.pdbName
824 + " do not satisfy the " + latticeSystem +
825 " lattice system restrictions and were ignored.\n");
826 sb.append(format(" A-axis: %18.15e\n", a));
827 sb.append(format(" B-axis: %18.15e\n", b));
828 sb.append(format(" C-axis: %18.15e\n", c));
829 sb.append(format(" Alpha: %18.15e\n", alpha));
830 sb.append(format(" Beta: %18.15e\n", beta));
831 sb.append(format(" Gamma: %18.15e\n", gamma));
832 logger.fine(sb.toString());
833 }
834 return false;
835 }
836 }
837
838 this.a = a;
839 this.b = b;
840 this.c = c;
841 this.alpha = alpha;
842 this.beta = beta;
843 this.gamma = gamma;
844
845 updateCrystal();
846
847 return true;
848 }
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867 public boolean changeUnitCellParametersAndVolume(
868 double a, double b, double c, double alpha, double beta, double gamma, double targetAUVolume) {
869 if (changeUnitCellParameters(a, b, c, alpha, beta, gamma)) {
870 double currentAUVolume = volume / getNumSymOps();
871 double scale = cbrt(targetAUVolume / currentAUVolume);
872 return changeUnitCellParameters(scale * a, scale * b, scale * c, alpha, beta, gamma);
873 }
874 return false;
875 }
876
877
878
879
880
881
882
883 @Override
884 public boolean equals(Object o) {
885 if (this == o) {
886 return true;
887 }
888 if (o == null || getClass() != o.getClass()) {
889 return false;
890 }
891 Crystal crystal = (Crystal) o;
892 return (a == crystal.a
893 && b == crystal.b
894 && c == crystal.c
895 && alpha == crystal.alpha
896 && beta == crystal.beta
897 && gamma == crystal.gamma
898 && spaceGroup.number == crystal.spaceGroup.number);
899 }
900
901
902
903
904
905
906 public boolean getCheckRestrictions() {
907 return checkRestrictions;
908 }
909
910
911
912
913
914
915 public void setCheckRestrictions(boolean checkRestrictions) {
916 this.checkRestrictions = checkRestrictions;
917 }
918
919
920
921
922
923
924
925 public double getDensity(double mass) {
926 int nSymm = spaceGroup.symOps.size();
927 return (mass * nSymm / AVOGADRO) * (1.0e24 / volume);
928 }
929
930
931
932
933
934
935 public int getNumSymOps() {
936 return spaceGroup.getNumberOfSymOps();
937 }
938
939
940
941
942
943
944
945
946
947 public double[] getRandomCartTranslation() {
948 double[] coords = {random(), random(), random()};
949 toCartesianCoordinates(coords, coords);
950 return coords;
951 }
952
953
954
955
956
957
958 public double getSpecialPositionCutoff() {
959 return specialPositionCutoff;
960 }
961
962
963
964
965
966
967 public double getSpecialPositionCutoff2() {
968 return specialPositionCutoff2;
969 }
970
971
972
973
974
975
976 public void setSpecialPositionCutoff(double cutoff) {
977 specialPositionCutoff = cutoff;
978 specialPositionCutoff2 = cutoff * cutoff;
979 }
980
981
982
983
984
985
986
987 public void getTransformationOperator(SymOp symOp, double[][] rotmat) {
988 double[][] rot = symOp.rot;
989 for (int i = 0; i < 3; i++) {
990 for (int j = 0; j < 3; j++) {
991 rotmat[i][j] = 0.0;
992 for (int k = 0; k < 3; k++) {
993 for (int l = 0; l < 3; l++) {
994 rotmat[i][j] += Ai[k][i] * rot[k][l] * A[j][l];
995 }
996 }
997 }
998 }
999 }
1000
1001
1002
1003
1004
1005
1006
1007 public Crystal getUnitCell() {
1008 return this;
1009 }
1010
1011
1012
1013
1014 @Override
1015 public int hashCode() {
1016 return Objects.hash(a, b, c, alpha, beta, gamma, spaceGroup.number);
1017 }
1018
1019
1020
1021
1022
1023
1024
1025
1026 public double image(double[] xyz, double[] xyz2) {
1027 double dx = xyz[0] - xyz2[0];
1028 double dy = xyz[1] - xyz2[1];
1029 double dz = xyz[2] - xyz2[2];
1030 return image(dx, dy, dz);
1031 }
1032
1033
1034
1035
1036
1037
1038
1039 public double image(final double[] xyz) {
1040 double x = xyz[0];
1041 double y = xyz[1];
1042 double z = xyz[2];
1043 if (aperiodic) {
1044 return x * x + y * y + z * z;
1045 }
1046 double xf = x * A00 + y * A10 + z * A20;
1047 double yf = y * A11 + z * A21;
1048 double zf = z * A22;
1049 xf = floor(abs(xf) + 0.5) * signum(-xf) + xf;
1050 yf = floor(abs(yf) + 0.5) * signum(-yf) + yf;
1051 zf = floor(abs(zf) + 0.5) * signum(-zf) + zf;
1052 x = xf * Ai00 + yf * Ai10 + zf * Ai20;
1053 y = yf * Ai11 + zf * Ai21;
1054 z = zf * Ai22;
1055 xyz[0] = x;
1056 xyz[1] = y;
1057 xyz[2] = z;
1058 return x * x + y * y + z * z;
1059 }
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069 public double image(double dx, double dy, double dz) {
1070 if (aperiodic) {
1071 return dx * dx + dy * dy + dz * dz;
1072 }
1073 double xf = dx * A00 + dy * A10 + dz * A20;
1074 double yf = dy * A11 + dz * A21;
1075 double zf = dz * A22;
1076 xf = floor(abs(xf) + 0.5) * signum(-xf) + xf;
1077 yf = floor(abs(yf) + 0.5) * signum(-yf) + yf;
1078 zf = floor(abs(zf) + 0.5) * signum(-zf) + zf;
1079 dx = xf * Ai00 + yf * Ai10 + zf * Ai20;
1080 dy = yf * Ai11 + zf * Ai21;
1081 dz = zf * Ai22;
1082 return dx * dx + dy * dy + dz * dz;
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092 public double minDistOverSymOps(double[] xyzA, double[] xyzB) {
1093 double dist = 0;
1094 for (int i = 0; i < 3; i++) {
1095 double dx = xyzA[i] - xyzB[i];
1096 dist += (dx * dx);
1097 }
1098 var symB = new double[3];
1099 for (SymOp symOp : spaceGroup.symOps) {
1100 applySymOp(xyzB, symB, symOp);
1101 for (int i = 0; i < 3; i++) {
1102 symB[i] -= xyzA[i];
1103 }
1104 double d = image(symB);
1105 dist = min(d, dist);
1106 }
1107 return sqrt(dist);
1108 }
1109
1110
1111
1112
1113
1114
1115
1116 public boolean perturbCellVectors(double[][] dStrain) {
1117
1118 double[][] AA = new double[3][3];
1119 double[][] newAi = new double[3][3];
1120
1121 for (int i = 0; i < 3; i++) {
1122 for (int j = 0; j < 3; j++) {
1123 AA[i][j] = getUnitCell().Ai[i][j];
1124 }
1125 }
1126
1127 for (int i = 0; i < 3; i++) {
1128 for (int j = 0; j < 3; j++) {
1129 for (int k = 0; k < 3; k++) {
1130 double Kronecker = 0.0;
1131 if (j == k) {
1132 Kronecker = 1.0;
1133 }
1134
1135 newAi[i][j] += (Kronecker + dStrain[j][k]) * AA[i][k];
1136 }
1137 }
1138 }
1139
1140 return setCellVectors(newAi);
1141 }
1142
1143
1144
1145
1146
1147
1148
1149
1150 public boolean randomParameters(double density, double mass) {
1151 double[] params = latticeSystem.resetUnitCellParams();
1152 boolean succeed = changeUnitCellParameters(params[0], params[1], params[2], params[3], params[4], params[5]);
1153 if (succeed) {
1154 setDensity(density, mass);
1155 }
1156 return succeed;
1157 }
1158
1159
1160
1161
1162
1163
1164 public void setAperiodic(boolean aperiodic) {
1165 this.aperiodic = aperiodic;
1166 }
1167
1168
1169
1170
1171
1172
1173
1174 public double[] getCellParametersFromVectors(double[][] cellVectors) {
1175
1176 double aa = length(cellVectors[0]);
1177 double bb = length(cellVectors[1]);
1178 double cc = length(cellVectors[2]);
1179
1180
1181 double aalpha = toDegrees(acos(dot(cellVectors[1], cellVectors[2]) / (bb * cc)));
1182 double bbeta = toDegrees(acos(dot(cellVectors[0], cellVectors[2]) / (aa * cc)));
1183 double ggamma = toDegrees(acos(dot(cellVectors[0], cellVectors[1]) / (aa * bb)));
1184
1185
1186 double[] params = new double[6];
1187 params[0] = aa;
1188 params[1] = bb;
1189 params[2] = cc;
1190 params[3] = aalpha;
1191 params[4] = bbeta;
1192 params[5] = ggamma;
1193 return params;
1194 }
1195
1196
1197
1198
1199
1200
1201
1202 public boolean setCellVectors(double[][] cellVectors) {
1203 double[] p = getCellParametersFromVectors(cellVectors);
1204 return changeUnitCellParameters(p[0], p[1], p[2], p[3], p[4], p[5]);
1205 }
1206
1207
1208
1209
1210
1211
1212
1213
1214 public boolean setCellVectorsAndVolume(double[][] cellVectors, double targetAUVolume) {
1215 if (setCellVectors(cellVectors)) {
1216 double currentAUVolume = volume / getNumSymOps();
1217 double scale = cbrt(targetAUVolume / currentAUVolume);
1218 return changeUnitCellParameters(scale * a, scale * b, scale * c, alpha, beta, gamma);
1219 } else {
1220 return false;
1221 }
1222 }
1223
1224
1225
1226
1227
1228
1229
1230 public void setDensity(double density, double mass) {
1231 double currentDensity = getDensity(mass);
1232 double scale = cbrt(currentDensity / density);
1233 changeUnitCellParameters(a * scale, b * scale, c * scale, alpha, beta, gamma);
1234 currentDensity = getDensity(mass);
1235 if (logger.isLoggable(Level.FINE)) {
1236 logger.fine(format(" Updated density %6.3f (g/cc) with unit cell %s.", currentDensity, toShortString()));
1237 }
1238 }
1239
1240
1241
1242
1243
1244
1245 public String toCRYST1() {
1246 return format(
1247 "CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f %10s\n",
1248 a, b, c, alpha, beta, gamma, padRight(spaceGroup.pdbName, 10));
1249 }
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262 public void toCartesianCoordinates(
1263 int n, double[] xf, double[] yf, double[] zf, double[] x, double[] y, double[] z) {
1264 for (int i = 0; i < n; i++) {
1265 double xi = xf[i];
1266 double yi = yf[i];
1267 double zi = zf[i];
1268 x[i] = xi * Ai00 + yi * Ai10 + zi * Ai20;
1269 y[i] = yi * Ai11 + zi * Ai21;
1270 z[i] = zi * Ai22;
1271 }
1272 }
1273
1274
1275
1276
1277
1278
1279
1280
1281 public void toCartesianCoordinates(int n, double[] frac, double[] cart) {
1282 int i3 = 0;
1283 for (int i = 0; i < n; i++) {
1284
1285 int iX = i3 + XX;
1286 int iY = i3 + YY;
1287 int iZ = i3 + ZZ;
1288 i3 += 3;
1289 double xf = frac[iX];
1290 double yf = frac[iY];
1291 double zf = frac[iZ];
1292 cart[iX] = xf * Ai00 + yf * Ai10 + zf * Ai20;
1293 cart[iY] = yf * Ai11 + zf * Ai21;
1294 cart[iZ] = zf * Ai22;
1295 }
1296 }
1297
1298
1299
1300
1301
1302
1303
1304 public void toCartesianCoordinates(double[] xf, double[] x) {
1305 double fx = xf[0];
1306 double fy = xf[1];
1307 double fz = xf[2];
1308 x[0] = fx * Ai00 + fy * Ai10 + fz * Ai20;
1309 x[1] = fy * Ai11 + fz * Ai21;
1310 x[2] = fz * Ai22;
1311 }
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324 public void toFractionalCoordinates(
1325 int n, double[] x, double[] y, double[] z, double[] xf, double[] yf, double[] zf) {
1326 for (int i = 0; i < n; i++) {
1327 double xc = x[i];
1328 double yc = y[i];
1329 double zc = z[i];
1330 xf[i] = xc * A00 + yc * A10 + zc * A20;
1331 yf[i] = yc * A11 + zc * A21;
1332 zf[i] = zc * A22;
1333 }
1334 }
1335
1336
1337
1338
1339
1340
1341
1342
1343 public void toFractionalCoordinates(int n, double[] cart, double[] frac) {
1344 int i3 = 0;
1345 for (int i = 0; i < n; i++) {
1346
1347 int iX = i3 + XX;
1348 int iY = i3 + YY;
1349 int iZ = i3 + ZZ;
1350 i3 += 3;
1351 double xc = cart[iX];
1352 double yc = cart[iY];
1353 double zc = cart[iZ];
1354 frac[iX] = xc * A00 + yc * A10 + zc * A20;
1355 frac[iY] = yc * A11 + zc * A21;
1356 frac[iZ] = zc * A22;
1357 }
1358 }
1359
1360
1361
1362
1363
1364
1365
1366 public void toFractionalCoordinates(double[] x, double[] xf) {
1367 double xc = x[0];
1368 double yc = x[1];
1369 double zc = x[2];
1370 xf[0] = xc * A00 + yc * A10 + zc * A20;
1371 xf[1] = yc * A11 + zc * A21;
1372 xf[2] = zc * A22;
1373 }
1374
1375
1376
1377
1378
1379
1380
1381 public void toPrimaryCell(double[] in, double[] out) {
1382 toFractionalCoordinates(in, out);
1383 out[0] = mod(out[0], 1.0);
1384 out[1] = mod(out[1], 1.0);
1385 out[2] = mod(out[2], 1.0);
1386 toCartesianCoordinates(out, out);
1387 }
1388
1389
1390
1391
1392
1393
1394 public String toShortString() {
1395 return format("%6.2f %6.2f %6.2f %6.2f %6.2f %6.2f", a, b, c, alpha, beta, gamma);
1396 }
1397
1398
1399
1400
1401 @Override
1402 public String toString() {
1403 StringBuilder sb = new StringBuilder("\n Unit Cell\n");
1404 sb.append(format(" A-axis length: %8.3f\n", a));
1405 sb.append(format(" B-axis length: %8.3f\n", b));
1406 sb.append(format(" C-axis length: %8.3f\n", c));
1407 sb.append(format(" Alpha: %8.3f\n", alpha));
1408 sb.append(format(" Beta: %8.3f\n", beta));
1409 sb.append(format(" Gamma: %8.3f\n", gamma));
1410 sb.append(" Space group\n");
1411 sb.append(format(" Number: %3d\n", spaceGroup.number));
1412 sb.append(format(" Symbol: %8s\n", spaceGroup.shortName));
1413 sb.append(
1414 format(" Number of Symmetry Operators: %3d\n", spaceGroup.getNumberOfSymOps()));
1415 sb.append(" Lattice Vectors\n");
1416 sb.append(format(" A: %8.3f, %8.3f, %8.3f\n", Ai00, 0.0, 0.0));
1417 sb.append(format(" B: %8.3f, %8.3f, %8.3f\n", Ai10, Ai11, 0.0));
1418 sb.append(format(" C: %8.3f, %8.3f, %8.3f\n", Ai20, Ai21, Ai22));
1419 sb.append(" Reciprocal Lattice Vectors\n");
1420 sb.append(format(" A*: %8.3f, %8.3f, %8.3f\n", A00, A10, A20));
1421 sb.append(format(" B*: %8.3f, %8.3f, %8.3f\n", 0.0, A11, A21));
1422 sb.append(format(" C*: %8.3f, %8.3f, %8.3f", 0.0, 0.0, A22));
1423 return sb.toString();
1424 }
1425
1426
1427
1428
1429 public final void updateCrystal() {
1430
1431 double cos_alpha;
1432 double sin_beta;
1433 double cos_beta;
1434 double sin_gamma;
1435 double cos_gamma;
1436 double beta_term;
1437 double gamma_term;
1438
1439 switch (crystalSystem) {
1440 default -> {
1441
1442 cos_alpha = 0.0;
1443 cos_beta = 0.0;
1444 sin_gamma = 1.0;
1445 cos_gamma = 0.0;
1446 beta_term = 0.0;
1447 gamma_term = 1.0;
1448 volume = a * b * c;
1449 dVdA = b * c;
1450 dVdB = a * c;
1451 dVdC = a * b;
1452 dVdAlpha = 0.0;
1453 dVdBeta = 0.0;
1454 dVdGamma = 0.0;
1455 }
1456 case MONOCLINIC -> {
1457 cos_alpha = 0.0;
1458 sin_beta = sin(toRadians(beta));
1459 cos_beta = cos(toRadians(beta));
1460 sin_gamma = 1.0;
1461 cos_gamma = 0.0;
1462 beta_term = 0.0;
1463 gamma_term = sin_beta;
1464 volume = sin_beta * a * b * c;
1465 dVdA = sin_beta * b * c;
1466 dVdB = sin_beta * a * c;
1467 dVdC = sin_beta * a * b;
1468 dVdAlpha = 0.0;
1469 dVdBeta = cos_beta * a * b * c;
1470 dVdGamma = 0.0;
1471 }
1472 case HEXAGONAL, TRICLINIC, TRIGONAL -> {
1473 double sin_alpha = sin(toRadians(alpha));
1474 cos_alpha = cos(toRadians(alpha));
1475 sin_beta = sin(toRadians(beta));
1476 cos_beta = cos(toRadians(beta));
1477 sin_gamma = sin(toRadians(gamma));
1478 cos_gamma = cos(toRadians(gamma));
1479 beta_term = (cos_alpha - cos_beta * cos_gamma) / sin_gamma;
1480 gamma_term = sqrt(sin_beta * sin_beta - beta_term * beta_term);
1481 volume = sin_gamma * gamma_term * a * b * c;
1482 dVdA = sin_gamma * gamma_term * b * c;
1483 dVdB = sin_gamma * gamma_term * a * c;
1484 dVdC = sin_gamma * gamma_term * a * b;
1485 double dbeta = 2.0 * sin_beta * cos_beta
1486 - (2.0 * (cos_alpha - cos_beta * cos_gamma) * sin_beta * cos_gamma)
1487 / (sin_gamma * sin_gamma);
1488 double dgamma1 = -2.0 * (cos_alpha - cos_beta * cos_gamma) * cos_beta / sin_gamma;
1489 double dgamma2 = cos_alpha - cos_beta * cos_gamma;
1490 dgamma2 *= dgamma2 * 2.0 * cos_gamma / (sin_gamma * sin_gamma * sin_gamma);
1491 dVdAlpha = (cos_alpha - cos_beta * cos_gamma) * sin_alpha
1492 / (sin_gamma * gamma_term) * a * b * c;
1493 dVdBeta = 0.5 * sin_gamma * dbeta / gamma_term * a * b * c;
1494 dVdGamma = (cos_gamma * gamma_term + 0.5 * sin_gamma * (dgamma1 + dgamma2) / gamma_term)
1495 * a * b * c;
1496 }
1497 }
1498
1499 G[0][0] = a * a;
1500 G[0][1] = a * b * cos_gamma;
1501 G[0][2] = a * c * cos_beta;
1502 G[1][0] = G[0][1];
1503 G[1][1] = b * b;
1504 G[1][2] = b * c * cos_alpha;
1505 G[2][0] = G[0][2];
1506 G[2][1] = G[1][2];
1507 G[2][2] = c * c;
1508
1509
1510 RealMatrix m = new Array2DRowRealMatrix(G, true);
1511 m = new LUDecomposition(m).getSolver().getInverse();
1512 Gstar = m.getData();
1513
1514
1515 Ai[0][0] = a;
1516 Ai[0][1] = 0.0;
1517 Ai[0][2] = 0.0;
1518
1519 Ai[1][0] = b * cos_gamma;
1520 Ai[1][1] = b * sin_gamma;
1521 Ai[1][2] = 0.0;
1522
1523 Ai[2][0] = c * cos_beta;
1524 Ai[2][1] = c * beta_term;
1525 Ai[2][2] = c * gamma_term;
1526
1527
1528 Ai00 = Ai[0][0];
1529
1530 Ai10 = Ai[1][0];
1531 Ai11 = Ai[1][1];
1532
1533 Ai20 = Ai[2][0];
1534 Ai21 = Ai[2][1];
1535 Ai22 = Ai[2][2];
1536
1537
1538 m = new Array2DRowRealMatrix(Ai, true);
1539 m = new LUDecomposition(m).getSolver().getInverse();
1540 A = m.getData();
1541
1542
1543
1544 A00 = A[0][0];
1545 A10 = A[1][0];
1546 A20 = A[2][0];
1547
1548 A11 = A[1][1];
1549 A21 = A[2][1];
1550
1551 A22 = A[2][2];
1552
1553
1554 double aStar = 1.0 / sqrt(A00 * A00 + A10 * A10 + A20 * A20);
1555 double bStar = 1.0 / sqrt(A11 * A11 + A21 * A21);
1556 double cStar = 1.0 / sqrt(A22 * A22);
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575 interfacialRadiusA = aStar / 2.0;
1576 interfacialRadiusB = bStar / 2.0;
1577 interfacialRadiusC = cStar / 2.0;
1578
1579 if (logger.isLoggable(Level.FINEST)) {
1580
1581 logger.finest(format(" Half lattice lengths: (%12.6f, %12.6f, %12.6f)",
1582 a / 2.0, b / 2.0, c / 2.0));
1583 logger.finest(format(" Interfacial radii: (%12.6f, %12.6f, %12.6f)",
1584 interfacialRadiusA, interfacialRadiusB, interfacialRadiusC));
1585 }
1586
1587 List<SymOp> symOps = spaceGroup.symOps;
1588 int nSymm = symOps.size();
1589 if (symOpsCartesian == null) {
1590 symOpsCartesian = new ArrayList<>(nSymm);
1591 } else {
1592 symOpsCartesian.clear();
1593 }
1594
1595 RealMatrix toFrac = new Array2DRowRealMatrix(A);
1596 RealMatrix toCart = new Array2DRowRealMatrix(Ai);
1597 for (SymOp symOp : symOps) {
1598 m = new Array2DRowRealMatrix(symOp.rot);
1599
1600 RealMatrix rotMat = m.preMultiply(toCart.transpose()).multiply(toFrac.transpose());
1601
1602 double[] tr = toCart.preMultiply(symOp.tr);
1603 symOpsCartesian.add(new SymOp(rotMat.getData(), tr));
1604 }
1605 }
1606 }