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.parameters;
39
40 import static ffx.numerics.math.DoubleMath.add;
41 import static ffx.numerics.math.DoubleMath.dot;
42 import static ffx.numerics.math.DoubleMath.normalize;
43 import static ffx.numerics.math.DoubleMath.sub;
44 import static ffx.potential.parameters.ForceField.ELEC_FORM.FIXED_CHARGE;
45 import static ffx.potential.parameters.ForceField.ForceFieldType.MULTIPOLE;
46 import static ffx.utilities.Constants.BOHR;
47 import static ffx.utilities.Constants.BOHR2;
48 import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
49 import static java.lang.Double.parseDouble;
50 import static java.lang.Integer.parseInt;
51 import static java.lang.String.format;
52 import static java.lang.System.arraycopy;
53 import static java.util.Arrays.fill;
54 import static org.apache.commons.math3.util.FastMath.abs;
55
56 import ffx.numerics.math.DoubleMath;
57 import ffx.potential.bonded.Atom;
58 import ffx.potential.parameters.ForceField.ELEC_FORM;
59 import ffx.utilities.FFXProperty;
60
61 import java.io.BufferedReader;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Comparator;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.logging.Level;
68 import java.util.logging.Logger;
69
70
71
72
73
74
75
76 @FFXProperty(name = "multipole", clazz = String[].class, propertyGroup = PotentialFunctionParameter, description = """
77 [5 lines with: 3 or 4 integers and 1 real; 3 reals; 1 real; 2 reals; 3 reals]
78 Provides the values for a set of atomic multipole parameters at a single site.
79 A complete keyword entry consists of five consecutive lines, the first line containing the multipole keyword and the four following lines.
80 The first line contains three integers which define the atom type on which the multipoles are centered,
81 and the Z-axis and X-axis defining atom types for this center.
82 The optional fourth integer contains the Y-axis defining atom type, and is only required for locally chiral multipole sites.
83 The real number on the first line gives the monopole (atomic charge) in electrons.
84 The second line contains three real numbers which give the X-, Y- and Z-components of the atomic dipole in electron-Ang.
85 The final three lines, consisting of one, two and three real numbers give the upper triangle of the
86 traceless atomic quadrupole tensor in electron-Ang^2.
87 """)
88 public final class MultipoleType extends BaseType implements Comparator<String> {
89
90
91
92
93 public static final double[] zeroM = new double[10];
94
95
96
97 public static final double[] zeroD = new double[3];
98
99
100
101 public static final double[][] zeroQ = new double[3][3];
102
103
104
105 public static final int t000 = 0;
106
107
108
109 public static final int t100 = 1;
110
111
112
113 public static final int t010 = 2;
114
115
116
117 public static final int t001 = 3;
118
119
120
121 public static final int t200 = 4;
122
123
124
125 public static final int t020 = 5;
126
127
128
129 public static final int t002 = 6;
130
131
132
133 public static final int t110 = 7;
134
135
136
137 public static final int t101 = 8;
138
139
140
141 public static final int t011 = 9;
142
143
144
145 public static final int t300 = 10;
146
147
148
149 public static final int t030 = 11;
150
151
152
153 public static final int t003 = 12;
154
155
156
157 public static final int t210 = 13;
158
159
160
161 public static final int t201 = 14;
162
163
164
165 public static final int t120 = 15;
166
167
168
169 public static final int t021 = 16;
170
171
172
173 public static final int t102 = 17;
174
175
176
177 public static final int t012 = 18;
178
179
180
181 public static final int t111 = 19;
182
183 private static final Logger logger = Logger.getLogger(MultipoleType.class.getName());
184
185
186
187 public final double charge;
188
189
190
191 public final double[] dipole;
192
193
194
195 public final double[][] quadrupole;
196
197
198
199 public final MultipoleFrameDefinition frameDefinition;
200
201
202
203 public final int[] frameAtomTypes;
204
205
206
207
208 private final double[] multipole;
209
210
211
212
213
214
215
216
217
218
219
220 public MultipoleType(double[] multipole, int[] frameAtomTypes,
221 MultipoleFrameDefinition frameDefinition, boolean convertFromBohr) {
222 super(MULTIPOLE, frameAtomTypes);
223 this.multipole = (convertFromBohr) ? bohrToElectronAngstroms(multipole) : multipole;
224 this.frameAtomTypes = frameAtomTypes;
225 this.frameDefinition = frameDefinition;
226 charge = multipole[t000];
227 dipole = unpackDipole(multipole);
228 quadrupole = unpackQuad(multipole);
229 checkMultipole();
230 }
231
232
233
234
235
236
237
238 public MultipoleType(MultipoleType multipoleType, double[] multipole) {
239 this(multipole, Arrays.copyOf(multipoleType.frameAtomTypes, multipoleType.frameAtomTypes.length),
240 multipoleType.frameDefinition, false);
241 }
242
243
244
245
246
247
248
249
250
251
252
253
254 public MultipoleType(double charge, double[] dipole, double[][] quadrupole, int[] frameAtomTypes,
255 MultipoleFrameDefinition frameDefinition, boolean convertFromBohr) {
256 super(MULTIPOLE, frameAtomTypes);
257 this.charge = charge;
258 if (convertFromBohr) {
259 this.multipole = bohrToElectronAngstroms(pack(charge, dipole, quadrupole));
260 this.dipole = unpackDipole(multipole);
261 this.quadrupole = unpackQuad(multipole);
262 } else {
263 this.multipole = pack(charge, dipole, quadrupole);
264 this.dipole = dipole;
265 this.quadrupole = quadrupole;
266 }
267 this.frameAtomTypes = frameAtomTypes;
268 this.frameDefinition = frameDefinition;
269 checkMultipole();
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284 public static boolean assignMultipole(ELEC_FORM elecForm, Atom atom, ForceField forceField,
285 double[] multipole, int i, int[][] axisAtom, MultipoleFrameDefinition[] frame) {
286 MultipoleType type = multipoleTypeFactory(elecForm, atom, forceField);
287 if (type == null) {
288 return false;
289 }
290 arraycopy(type.getMultipole(), 0, multipole, 0, 10);
291 axisAtom[i] = atom.getAxisAtomIndices();
292 frame[i] = atom.getMultipoleType().frameDefinition;
293 return true;
294 }
295
296
297
298
299
300
301
302
303
304
305 public static MultipoleType averageTypes(MultipoleType multipoleType1,
306 MultipoleType multipoleType2, int[] multipoleFrameTypes) {
307 if (multipoleType1.frameDefinition != multipoleType2.frameDefinition) {
308 return null;
309 }
310 MultipoleType[] types = {multipoleType1, multipoleType2};
311 double[] weights = {0.5, 0.5};
312 double[] averagedMultipole = weightMultipole(types, weights);
313 if (averagedMultipole == null) {
314 return null;
315 }
316 return new MultipoleType(averagedMultipole, multipoleFrameTypes, multipoleType1.frameDefinition,
317 false);
318 }
319
320
321
322
323
324
325
326
327
328
329 public static boolean checkMultipoleChirality(MultipoleFrameDefinition frame, double[] localOrigin,
330 double[][] frameCoords) {
331 if (frame != MultipoleFrameDefinition.ZTHENX) {
332 return false;
333 }
334 double[] zAxis = new double[3];
335 double[] xAxis = new double[3];
336 double[] yAxis = new double[3];
337 double[] yMinOrigin = new double[3];
338 zAxis[0] = frameCoords[0][0];
339 zAxis[1] = frameCoords[0][1];
340 zAxis[2] = frameCoords[0][2];
341 xAxis[0] = frameCoords[1][0];
342 xAxis[1] = frameCoords[1][1];
343 xAxis[2] = frameCoords[1][2];
344 yAxis[0] = frameCoords[2][0];
345 yAxis[1] = frameCoords[2][1];
346 yAxis[2] = frameCoords[2][2];
347 sub(localOrigin, yAxis, yMinOrigin);
348 sub(zAxis, yAxis, zAxis);
349 sub(xAxis, yAxis, xAxis);
350 double c1 = zAxis[1] * xAxis[2] - zAxis[2] * xAxis[1];
351 double c2 = xAxis[1] * yMinOrigin[2] - xAxis[2] * yMinOrigin[1];
352 double c3 = yMinOrigin[1] * zAxis[2] - yMinOrigin[2] * zAxis[1];
353 double vol = yMinOrigin[0] * c1 + zAxis[0] * c2 + xAxis[0] * c3;
354 return (vol < 0.0);
355 }
356
357
358
359
360
361
362
363
364
365 public static double[][] getRotationMatrix(MultipoleFrameDefinition frame, double[] localOrigin,
366 double[][] frameCoords) {
367 double[][] rotMat = new double[3][3];
368 getRotationMatrix(frame, localOrigin, frameCoords, rotMat);
369 return rotMat;
370 }
371
372
373
374
375
376
377
378
379
380 public static void getRotationMatrix(MultipoleFrameDefinition frame, double[] localOrigin,
381 double[][] frameCoords, double[][] rotMat) {
382 double[] zAxis = new double[3];
383 double[] xAxis = new double[3];
384 double[] yAxis = new double[3];
385
386
387 rotMat[0][0] = 1.0;
388 rotMat[1][0] = 0.0;
389 rotMat[2][0] = 0.0;
390 rotMat[0][1] = 0.0;
391 rotMat[1][1] = 1.0;
392 rotMat[2][1] = 0.0;
393 rotMat[0][2] = 0.0;
394 rotMat[1][2] = 0.0;
395 rotMat[2][2] = 1.0;
396
397 switch (frame) {
398 case NONE -> {
399 return;
400 }
401 case BISECTOR -> {
402 zAxis[0] = frameCoords[0][0];
403 zAxis[1] = frameCoords[0][1];
404 zAxis[2] = frameCoords[0][2];
405 xAxis[0] = frameCoords[1][0];
406 xAxis[1] = frameCoords[1][1];
407 xAxis[2] = frameCoords[1][2];
408 sub(zAxis, localOrigin, zAxis);
409 normalize(zAxis, zAxis);
410 sub(xAxis, localOrigin, xAxis);
411 normalize(xAxis, xAxis);
412 add(xAxis, zAxis, zAxis);
413 normalize(zAxis, zAxis);
414 rotMat[0][2] = zAxis[0];
415 rotMat[1][2] = zAxis[1];
416 rotMat[2][2] = zAxis[2];
417 double dot = dot(xAxis, zAxis);
418 DoubleMath.scale(zAxis, dot, zAxis);
419 sub(xAxis, zAxis, xAxis);
420 normalize(xAxis, xAxis);
421 }
422 case ZTHENBISECTOR -> {
423 zAxis[0] = frameCoords[0][0];
424 zAxis[1] = frameCoords[0][1];
425 zAxis[2] = frameCoords[0][2];
426 xAxis[0] = frameCoords[1][0];
427 xAxis[1] = frameCoords[1][1];
428 xAxis[2] = frameCoords[1][2];
429 yAxis[0] = frameCoords[2][0];
430 yAxis[1] = frameCoords[2][1];
431 yAxis[2] = frameCoords[2][2];
432
433 sub(zAxis, localOrigin, zAxis);
434 normalize(zAxis, zAxis);
435 rotMat[0][2] = zAxis[0];
436 rotMat[1][2] = zAxis[1];
437 rotMat[2][2] = zAxis[2];
438 sub(xAxis, localOrigin, xAxis);
439 normalize(xAxis, xAxis);
440 sub(yAxis, localOrigin, yAxis);
441 normalize(yAxis, yAxis);
442
443 add(xAxis, yAxis, xAxis);
444 normalize(xAxis, xAxis);
445 var dot = dot(xAxis, zAxis);
446 DoubleMath.scale(zAxis, dot, zAxis);
447 sub(xAxis, zAxis, xAxis);
448 normalize(xAxis, xAxis);
449 }
450 case ZONLY -> {
451 zAxis[0] = frameCoords[0][0];
452 zAxis[1] = frameCoords[0][1];
453 zAxis[2] = frameCoords[0][2];
454 sub(zAxis, localOrigin, zAxis);
455 normalize(zAxis, zAxis);
456 rotMat[0][2] = zAxis[0];
457 rotMat[1][2] = zAxis[1];
458 rotMat[2][2] = zAxis[2];
459
460 xAxis[0] = 1.0;
461 xAxis[1] = 0.0;
462 xAxis[2] = 0.0;
463
464
465 var dot = rotMat[0][2];
466 if (abs(dot) > 0.866) {
467 xAxis[0] = 0.0;
468 xAxis[1] = 1.0;
469 dot = rotMat[1][2];
470 }
471 DoubleMath.scale(zAxis, dot, zAxis);
472 sub(xAxis, zAxis, xAxis);
473 normalize(xAxis, xAxis);
474 }
475
476 case THREEFOLD -> {
477 zAxis[0] = frameCoords[0][0];
478 zAxis[1] = frameCoords[0][1];
479 zAxis[2] = frameCoords[0][2];
480 sub(zAxis, localOrigin, zAxis);
481 normalize(zAxis, zAxis);
482 xAxis[0] = frameCoords[1][0];
483 xAxis[1] = frameCoords[1][1];
484 xAxis[2] = frameCoords[1][2];
485 sub(xAxis, localOrigin, xAxis);
486 normalize(xAxis, xAxis);
487 yAxis[0] = frameCoords[2][0];
488 yAxis[1] = frameCoords[2][1];
489 yAxis[2] = frameCoords[2][2];
490 sub(yAxis, localOrigin, yAxis);
491 normalize(yAxis, yAxis);
492 add(zAxis, xAxis, zAxis);
493 add(zAxis, yAxis, zAxis);
494 normalize(zAxis, zAxis);
495 rotMat[0][2] = zAxis[0];
496 rotMat[1][2] = zAxis[1];
497 rotMat[2][2] = zAxis[2];
498 var dot = dot(xAxis, zAxis);
499 DoubleMath.scale(zAxis, dot, zAxis);
500 sub(xAxis, zAxis, xAxis);
501 normalize(xAxis, xAxis);
502 }
503 case ZTHENX -> {
504 zAxis[0] = frameCoords[0][0];
505 zAxis[1] = frameCoords[0][1];
506 zAxis[2] = frameCoords[0][2];
507 xAxis[0] = frameCoords[1][0];
508 xAxis[1] = frameCoords[1][1];
509 xAxis[2] = frameCoords[1][2];
510 sub(zAxis, localOrigin, zAxis);
511 normalize(zAxis, zAxis);
512 rotMat[0][2] = zAxis[0];
513 rotMat[1][2] = zAxis[1];
514 rotMat[2][2] = zAxis[2];
515 sub(xAxis, localOrigin, xAxis);
516 var dot = dot(xAxis, zAxis);
517 DoubleMath.scale(zAxis, dot, zAxis);
518 sub(xAxis, zAxis, xAxis);
519 normalize(xAxis, xAxis);
520 }
521 default -> throw new IllegalStateException("Unexpected value: " + frame);
522 }
523
524 rotMat[0][0] = xAxis[0];
525 rotMat[1][0] = xAxis[1];
526 rotMat[2][0] = xAxis[2];
527
528 rotMat[0][1] = rotMat[2][0] * rotMat[1][2] - rotMat[1][0] * rotMat[2][2];
529 rotMat[1][1] = rotMat[0][0] * rotMat[2][2] - rotMat[2][0] * rotMat[0][2];
530 rotMat[2][1] = rotMat[1][0] * rotMat[0][2] - rotMat[0][0] * rotMat[1][2];
531 }
532
533
534
535
536
537
538
539
540
541 public static MultipoleType multipoleTypeFactory(ELEC_FORM elecForm, Atom atom,
542 ForceField forceField) {
543 AtomType atomType = atom.getAtomType();
544 if (atomType == null) {
545 String message = " Multipoles can only be assigned to atoms that have been typed.";
546 logger.severe(message);
547 return null;
548 }
549
550 if (elecForm != FIXED_CHARGE) {
551 PolarizeType polarizeType = forceField.getPolarizeType(atomType.getKey());
552 if (polarizeType != null) {
553 atom.setPolarizeType(polarizeType);
554 } else {
555 String message = " No polarization type was found for " + atom;
556 logger.info(message);
557 double polarizability = 0.0;
558 double thole = 0.0;
559 double ddp = 0.0;
560 polarizeType = new PolarizeType(atomType.type, polarizability, thole, ddp, null);
561 forceField.addForceFieldType(polarizeType);
562 atom.setPolarizeType(polarizeType);
563 }
564 }
565
566
567 String key1 = atomType.getKey();
568 MultipoleType multipoleType = forceField.getMultipoleType(key1);
569 if (multipoleType != null) {
570 atom.setMultipoleType(multipoleType);
571 atom.setAxisAtoms((Atom[]) null);
572 return multipoleType;
573 }
574
575
576 List<Atom> n12 = atom.get12List();
577
578
579 if (n12 == null || n12.isEmpty()) {
580 String message = "Multipoles can only be assigned after bonded relationships are defined.\n";
581 logger.severe(message);
582 return null;
583 }
584
585
586 for (Atom atom2 : n12) {
587 String key = key1 + " " + atom2.getAtomType().getKey();
588 multipoleType = forceField.getMultipoleType(key);
589 if (multipoleType != null) {
590 atom.setMultipoleType(multipoleType);
591 atom.setAxisAtoms(atom2);
592 return multipoleType;
593 }
594 }
595
596
597 for (Atom atom2 : n12) {
598 String key2 = atom2.getAtomType().getKey();
599 for (Atom atom3 : n12) {
600 if (atom2 == atom3) {
601 continue;
602 }
603 String key3 = atom3.getAtomType().getKey();
604 String key = key1 + " " + key2 + " " + key3;
605 multipoleType = forceField.getMultipoleType(key);
606 if (multipoleType != null) {
607 atom.setMultipoleType(multipoleType);
608 atom.setAxisAtoms(atom2, atom3);
609 return multipoleType;
610 }
611 }
612 }
613
614
615 for (Atom atom2 : n12) {
616 String key2 = atom2.getAtomType().getKey();
617 for (Atom atom3 : n12) {
618 if (atom2 == atom3) {
619 continue;
620 }
621 String key3 = atom3.getAtomType().getKey();
622 for (Atom atom4 : n12) {
623 if (atom2 == atom4 || atom3 == atom4) {
624 continue;
625 }
626 String key4 = atom4.getAtomType().getKey();
627 String key = key1 + " " + key2 + " " + key3 + " " + key4;
628 multipoleType = forceField.getMultipoleType(key);
629 if (multipoleType != null) {
630 atom.setMultipoleType(multipoleType);
631 atom.setAxisAtoms(atom2, atom3, atom4);
632 return multipoleType;
633 }
634 }
635 }
636 }
637
638 List<Atom> n13 = atom.get13List();
639
640
641
642 for (Atom atom2 : n12) {
643 String key2 = atom2.getAtomType().getKey();
644 for (Atom atom3 : n13) {
645 String key3 = atom3.getAtomType().getKey();
646 String key = key1 + " " + key2 + " " + key3;
647 multipoleType = forceField.getMultipoleType(key);
648 if (multipoleType != null) {
649 atom.setMultipoleType(multipoleType);
650 atom.setAxisAtoms(atom2, atom3);
651 return multipoleType;
652 }
653 for (Atom atom4 : n13) {
654 if (atom4 != null && atom4 != atom3) {
655 String key4 = atom4.getAtomType().getKey();
656 key = key1 + " " + key2 + " " + key3 + " " + key4;
657 multipoleType = forceField.getMultipoleType(key);
658 if (multipoleType != null) {
659 atom.setMultipoleType(multipoleType);
660 atom.setAxisAtoms(atom2, atom3, atom4);
661 return multipoleType;
662 }
663 }
664 }
665 }
666 }
667 return null;
668 }
669
670
671
672
673
674
675 public static void assignAxisAtoms(Atom atom) {
676 MultipoleType multipoleType = atom.getMultipoleType();
677 String mutipoleKey = multipoleType.getKey();
678 String atomKey = atom.getAtomType().getKey();
679 String[] types = mutipoleKey.split(" +");
680 int numAxisAtoms = types.length - 1;
681
682 if (numAxisAtoms == 0) {
683
684 atom.setAxisAtoms((Atom[]) null);
685 return;
686 }
687
688
689 List<Atom> n12 = atom.get12List();
690 if (n12 == null || n12.isEmpty()) {
691 String message = "Multipoles can only be assigned after bonded relationships are defined.\n";
692 logger.severe(message);
693 return;
694 }
695
696
697 for (Atom atom2 : n12) {
698 String key = atomKey + " " + atom2.getAtomType().getKey();
699 if (key.equalsIgnoreCase(mutipoleKey)) {
700 atom.setAxisAtoms(atom2);
701 return;
702 }
703 }
704
705
706 for (Atom atom2 : n12) {
707 String key2 = atom2.getAtomType().getKey();
708 for (Atom atom3 : n12) {
709 if (atom2 == atom3) {
710 continue;
711 }
712 String key3 = atom3.getAtomType().getKey();
713 String key = atomKey + " " + key2 + " " + key3;
714 if (key.equalsIgnoreCase(mutipoleKey)) {
715 atom.setAxisAtoms(atom2, atom3);
716 return;
717 }
718 }
719 }
720
721
722 for (Atom atom2 : n12) {
723 String key2 = atom2.getAtomType().getKey();
724 for (Atom atom3 : n12) {
725 if (atom2 == atom3) {
726 continue;
727 }
728 String key3 = atom3.getAtomType().getKey();
729 for (Atom atom4 : n12) {
730 if (atom2 == atom4 || atom3 == atom4) {
731 continue;
732 }
733 String key4 = atom4.getAtomType().getKey();
734 String key = atomKey + " " + key2 + " " + key3 + " " + key4;
735 if (key.equalsIgnoreCase(mutipoleKey)) {
736 atom.setAxisAtoms(atom2, atom3);
737 return;
738 }
739 }
740 }
741 }
742
743 List<Atom> n13 = atom.get13List();
744
745
746 for (Atom atom2 : n12) {
747 String key2 = atom2.getAtomType().getKey();
748 for (Atom atom3 : n13) {
749 String key3 = atom3.getAtomType().getKey();
750 String key = atomKey + " " + key2 + " " + key3;
751 if (key.equalsIgnoreCase(mutipoleKey)) {
752 atom.setAxisAtoms(atom2, atom3);
753 return;
754 }
755 for (Atom atom4 : n13) {
756 if (atom4 != null && atom4 != atom3) {
757 String key4 = atom4.getAtomType().getKey();
758 key = atomKey + " " + key2 + " " + key3 + " " + key4;
759 if (key.equalsIgnoreCase(mutipoleKey)) {
760 atom.setAxisAtoms(atom2, atom3, atom4);
761 return;
762 }
763 }
764 }
765 }
766 }
767
768 String message = format(" Assignment of axis atoms failed for %s %s.", atom, multipoleType);
769 logger.severe(message);
770 }
771
772
773
774
775
776
777
778
779
780
781 public static MultipoleType parse(String input, String[] tokens, BufferedReader br) {
782 if (tokens == null || tokens.length < 3 || tokens.length > 6) {
783 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
784 return null;
785 }
786
787 try {
788 int nTokens = tokens.length;
789 ArrayList<Integer> frameAtoms = new ArrayList<>();
790
791
792 for (int i = 1; i < nTokens - 1; i++) {
793 int frameType = parseInt(tokens[i]);
794
795 if (frameType != 0) {
796 frameAtoms.add(frameType);
797 }
798 }
799 int nAtomTypes = frameAtoms.size();
800 int[] atomTypes = new int[nAtomTypes];
801 for (int i = 0; i < nAtomTypes; i++) {
802 atomTypes[i] = frameAtoms.get(i);
803 }
804
805 double charge = parseDouble(tokens[nTokens - 1]);
806
807 MultipoleFrameDefinition frameDefinition = null;
808 if (nAtomTypes == 1) {
809 frameDefinition = MultipoleFrameDefinition.NONE;
810 } else if (nAtomTypes == 2) {
811 frameDefinition = MultipoleFrameDefinition.ZONLY;
812 } else if (nAtomTypes == 3) {
813
814 if (atomTypes[1] < 0 || atomTypes[2] < 0) {
815 frameDefinition = MultipoleFrameDefinition.BISECTOR;
816 } else {
817 frameDefinition = MultipoleFrameDefinition.ZTHENX;
818 }
819 } else if (nAtomTypes == 4) {
820
821 if (atomTypes[2] < 0 && atomTypes[3] < 0) {
822 frameDefinition = MultipoleFrameDefinition.ZTHENBISECTOR;
823 if (atomTypes[1] < 0) {
824 frameDefinition = MultipoleFrameDefinition.THREEFOLD;
825 }
826 }
827 }
828
829
830 if (frameDefinition == null) {
831 logger.log(Level.FINE, "Ambiguous MULTIPOLE type:\n{0}", input);
832 frameDefinition = MultipoleFrameDefinition.ZTHENX;
833 }
834
835 for (int i = 0; i < nAtomTypes; i++) {
836 atomTypes[i] = abs(atomTypes[i]);
837 }
838
839 input = br.readLine().split("#")[0];
840 tokens = input.trim().split(" +");
841 if (tokens.length != 3) {
842 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
843 return null;
844 }
845 double[] dipole = new double[3];
846 dipole[0] = parseDouble(tokens[0]);
847 dipole[1] = parseDouble(tokens[1]);
848 dipole[2] = parseDouble(tokens[2]);
849 input = br.readLine().split("#")[0];
850 tokens = input.trim().split(" +");
851 if (tokens.length != 1) {
852 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
853 return null;
854 }
855 double[][] quadrupole = new double[3][3];
856 quadrupole[0][0] = parseDouble(tokens[0]);
857 input = br.readLine().split("#")[0];
858 tokens = input.trim().split(" +");
859 if (tokens.length != 2) {
860 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
861 return null;
862 }
863 quadrupole[1][0] = parseDouble(tokens[0]);
864 quadrupole[1][1] = parseDouble(tokens[1]);
865 input = br.readLine().split("#")[0];
866 tokens = input.trim().split(" +");
867 if (tokens.length != 3) {
868 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
869 return null;
870 }
871 quadrupole[2][0] = parseDouble(tokens[0]);
872 quadrupole[2][1] = parseDouble(tokens[1]);
873 quadrupole[2][2] = parseDouble(tokens[2]);
874
875 quadrupole[0][1] = quadrupole[1][0];
876 quadrupole[0][2] = quadrupole[2][0];
877 quadrupole[1][2] = quadrupole[2][1];
878 return new MultipoleType(charge, dipole, quadrupole, atomTypes, frameDefinition, true);
879 } catch (Exception e) {
880 String message = "Exception parsing MULTIPOLE type:\n" + input + "\n";
881 logger.log(Level.SEVERE, message, e);
882 }
883 return null;
884 }
885
886
887
888
889
890
891
892
893
894 public static MultipoleType parse(String input, String[] tokens) {
895 if (tokens == null || tokens.length < 12 || tokens.length > 15) {
896 logger.log(Level.WARNING, "Invalid MULTIPOLE type:\n{0}", input);
897 return null;
898 }
899
900 try {
901 int nTokens = tokens.length;
902 ArrayList<Integer> frameAtoms = new ArrayList<>();
903
904
905 for (int i = 1; i < nTokens - 10; i++) {
906 int frameType = parseInt(tokens[i]);
907
908 if (frameType != 0) {
909 frameAtoms.add(frameType);
910 }
911 }
912 int nAtomTypes = frameAtoms.size();
913 int[] atomTypes = new int[nAtomTypes];
914 for (int i = 0; i < nAtomTypes; i++) {
915 atomTypes[i] = frameAtoms.get(i);
916 }
917
918 MultipoleFrameDefinition frameDefinition = null;
919 if (nAtomTypes == 1) {
920 frameDefinition = MultipoleFrameDefinition.NONE;
921 } else if (nAtomTypes == 2) {
922 frameDefinition = MultipoleFrameDefinition.ZONLY;
923 } else if (nAtomTypes == 3) {
924
925 if (atomTypes[1] < 0 || atomTypes[2] < 0) {
926 frameDefinition = MultipoleFrameDefinition.BISECTOR;
927 } else {
928 frameDefinition = MultipoleFrameDefinition.ZTHENX;
929 }
930 } else if (nAtomTypes == 4) {
931
932 if (atomTypes[2] < 0 && atomTypes[3] < 0) {
933 frameDefinition = MultipoleFrameDefinition.ZTHENBISECTOR;
934 if (atomTypes[1] < 0) {
935 frameDefinition = MultipoleFrameDefinition.THREEFOLD;
936 }
937 }
938 }
939
940
941 if (frameDefinition == null) {
942 logger.log(Level.FINE, "Ambiguous MULTIPOLE type:\n{0}", input);
943 frameDefinition = MultipoleFrameDefinition.ZTHENX;
944 }
945
946 for (int i = 0; i < nAtomTypes; i++) {
947 atomTypes[i] = abs(atomTypes[i]);
948 }
949
950 double[] dipole = new double[3];
951 double[][] quadrupole = new double[3][3];
952 double charge = parseDouble(tokens[nTokens - 10]);
953 dipole[0] = parseDouble(tokens[nTokens - 9]);
954 dipole[1] = parseDouble(tokens[nTokens - 8]);
955 dipole[2] = parseDouble(tokens[nTokens - 7]);
956 quadrupole[0][0] = parseDouble(tokens[nTokens - 6]);
957 quadrupole[1][0] = parseDouble(tokens[nTokens - 5]);
958 quadrupole[1][1] = parseDouble(tokens[nTokens - 4]);
959 quadrupole[2][0] = parseDouble(tokens[nTokens - 3]);
960 quadrupole[2][1] = parseDouble(tokens[nTokens - 2]);
961 quadrupole[2][2] = parseDouble(tokens[nTokens - 1]);
962
963 quadrupole[0][1] = quadrupole[1][0];
964 quadrupole[0][2] = quadrupole[2][0];
965 quadrupole[1][2] = quadrupole[2][1];
966 return new MultipoleType(charge, dipole, quadrupole, atomTypes, frameDefinition, true);
967 } catch (Exception e) {
968 String message = "Exception parsing MULTIPOLE type:\n" + input + "\n";
969 logger.log(Level.SEVERE, message, e);
970 }
971 return null;
972 }
973
974
975
976
977
978
979
980
981 public static MultipoleType parseChargeType(String input, String[] tokens) {
982 if (tokens.length < 3) {
983 logger.log(Level.WARNING, "Invalid CHARGE type:\n{0}", input);
984 } else {
985 try {
986 int[] atomTypes = new int[]{parseInt(tokens[1])};
987 double charge = parseDouble(tokens[2]);
988 double[] dipole = new double[3];
989 double[][] quadrupole = new double[3][3];
990 MultipoleFrameDefinition frameDefinition = MultipoleFrameDefinition.NONE;
991 return new MultipoleType(charge, dipole, quadrupole, atomTypes, frameDefinition, true);
992 } catch (NumberFormatException e) {
993 String message = "Exception parsing CHARGE type:\n" + input + "\n";
994 logger.log(Level.SEVERE, message, e);
995 }
996 }
997 return null;
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007 public static void rotateDipole(double[][] rotMat, double[] dipole, double[] rotatedDipole) {
1008 for (int i = 0; i < 3; i++) {
1009 double[] rotmati = rotMat[i];
1010 for (int j = 0; j < 3; j++) {
1011 rotatedDipole[i] += rotmati[j] * dipole[j];
1012 }
1013 }
1014 }
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025 public static void rotateMultipole(double[][] rotmat, double[] dipole, double[][] quadrupole,
1026 double[] rotatedDipole, double[][] rotatedQuadrupole) {
1027
1028
1029 fill(rotatedDipole, 0.0);
1030 for (int i = 0; i < 3; i++) {
1031 fill(rotatedQuadrupole[i], 0.0);
1032 }
1033
1034
1035 for (int i = 0; i < 3; i++) {
1036 double[] rotmati = rotmat[i];
1037 double[] quadrupolei = rotatedQuadrupole[i];
1038 rotatedDipole[i] = 0.0;
1039 for (int j = 0; j < 3; j++) {
1040 double[] rotmatj = rotmat[j];
1041 rotatedDipole[i] += rotmati[j] * dipole[j];
1042 if (j < i) {
1043 quadrupolei[j] = rotatedQuadrupole[j][i];
1044 } else {
1045 for (int k = 0; k < 3; k++) {
1046 double[] localQuadrupolek = quadrupole[k];
1047 quadrupolei[j] +=
1048 rotmati[k] * (rotmatj[0] * localQuadrupolek[0] + rotmatj[1] * localQuadrupolek[1]
1049 + rotmatj[2] * localQuadrupolek[2]);
1050 }
1051 }
1052 }
1053 }
1054 }
1055
1056
1057
1058
1059
1060
1061
1062
1063 public static double[] scale(MultipoleType type, double[] cdtScales) {
1064 return scale(type.getMultipole(), cdtScales);
1065 }
1066
1067
1068
1069
1070
1071
1072
1073
1074 public static double[] scale(double[] multipole, double[] cdtScales) {
1075 double chargeScale = cdtScales[0];
1076 double dipoleScale = cdtScales[1];
1077 double quadScale = cdtScales[2];
1078 return new double[]{multipole[t000] * chargeScale, multipole[t100] * dipoleScale,
1079 multipole[t010] * dipoleScale, multipole[t001] * dipoleScale, multipole[t200] * quadScale,
1080 multipole[t020] * quadScale, multipole[t002] * quadScale, multipole[t110] * quadScale,
1081 multipole[t101] * quadScale, multipole[t011] * quadScale};
1082 }
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 public static MultipoleType weightMultipoleTypes(MultipoleType[] types, double[] weights,
1095 int[] frameAtomTypes) {
1096 double[] weightedMultipole = weightMultipole(types, weights);
1097 if (weightedMultipole == null) {
1098 return null;
1099 }
1100 return new MultipoleType(weightedMultipole, frameAtomTypes, types[0].frameDefinition, false);
1101 }
1102
1103 private static double[] bohrToElectronAngstroms(double[] multipole) {
1104 return new double[]{multipole[t000], multipole[t100] *= BOHR, multipole[t010] *= BOHR,
1105 multipole[t001] *= BOHR, multipole[t200] *= BOHR2, multipole[t020] *= BOHR2,
1106 multipole[t002] *= BOHR2, multipole[t110] *= BOHR2, multipole[t101] *= BOHR2,
1107 multipole[t011] *= BOHR2};
1108 }
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118 private static double[] pack(double charge, double[] dipole, double[][] quad) {
1119 return new double[]{charge, dipole[0], dipole[1], dipole[2], quad[0][0], quad[1][1], quad[2][2],
1120 quad[0][1], quad[0][2], quad[1][2]};
1121 }
1122
1123
1124
1125
1126 private static double[] unpackDipole(double[] mpole) {
1127 return new double[]{mpole[t100], mpole[t010], mpole[t001]};
1128 }
1129
1130
1131
1132
1133 private static double[][] unpackQuad(double[] mpole) {
1134 return new double[][]{{mpole[t200], mpole[t110], mpole[t101]},
1135 {mpole[t110], mpole[t020], mpole[t011]}, {mpole[t101], mpole[t011], mpole[t002]}};
1136 }
1137
1138
1139
1140
1141
1142
1143
1144
1145 private static double[] weightMultipole(MultipoleType[] types, double[] weights) {
1146 if (types == null || weights == null || types.length != weights.length) {
1147 throw new IllegalArgumentException();
1148 }
1149 if (Arrays.asList(types).contains(null)) {
1150
1151 return null;
1152 }
1153 for (MultipoleType type : types) {
1154 if (type.frameDefinition != types[0].frameDefinition) {
1155 logger.warning(
1156 format("Multipole frame definition mismatch during weighting:\n\t%s->%s,\n\t%s->%s",
1157 types[0], types[0].frameDefinition.toString(), type,
1158 type.frameDefinition.toString()));
1159 throw new IllegalArgumentException();
1160 }
1161 }
1162 double[] weightedMultipole = new double[10];
1163 fill(weightedMultipole, 0.0);
1164 for (int idx = 0; idx < types.length; idx++) {
1165 double[] multipole = types[idx].getMultipole();
1166 for (int comp = 0; comp < 10; comp++) {
1167 weightedMultipole[comp] += weights[idx] * multipole[comp];
1168 }
1169 }
1170 return weightedMultipole;
1171 }
1172
1173
1174
1175
1176 @Override
1177 public int compare(String s1, String s2) {
1178 String[] keys1 = s1.split(" ");
1179 String[] keys2 = s2.split(" ");
1180
1181 int len = keys1.length;
1182 if (keys1.length > keys2.length) {
1183 len = keys2.length;
1184 }
1185 int[] c1 = new int[len];
1186 int[] c2 = new int[len];
1187 for (int i = 0; i < len; i++) {
1188 c1[i] = abs(parseInt(keys1[i]));
1189 c2[i] = abs(parseInt(keys2[i]));
1190 if (c1[i] < c2[i]) {
1191 return -1;
1192 } else if (c1[i] > c2[i]) {
1193 return 1;
1194 }
1195 }
1196
1197 if (keys1.length < keys2.length) {
1198 return -1;
1199 } else if (keys1.length > keys2.length) {
1200 return 1;
1201 }
1202
1203 return 0;
1204 }
1205
1206
1207
1208
1209 @Override
1210 public boolean equals(Object o) {
1211 if (this == o) {
1212 return true;
1213 }
1214 if (o == null || getClass() != o.getClass()) {
1215 return false;
1216 }
1217 MultipoleType multipoleType = (MultipoleType) o;
1218 return Arrays.equals(frameAtomTypes, multipoleType.frameAtomTypes);
1219 }
1220
1221
1222
1223
1224
1225
1226 public double getCharge() {
1227 return multipole[t000];
1228 }
1229
1230
1231
1232
1233
1234
1235 public double[] getDipole() {
1236 return new double[]{multipole[t100], multipole[t010], multipole[t001]};
1237 }
1238
1239
1240
1241
1242
1243
1244
1245 public double[] getMultipole() {
1246 return new double[]{multipole[t000], multipole[t100], multipole[t010], multipole[t001],
1247 multipole[t200], multipole[t020], multipole[t002], multipole[t110], multipole[t101],
1248 multipole[t011]};
1249 }
1250
1251
1252
1253
1254
1255
1256
1257 public double[][] getQuadrupole() {
1258 return new double[][]{{multipole[t200], multipole[t110], multipole[t101]},
1259 {multipole[t110], multipole[t020], multipole[t011]},
1260 {multipole[t101], multipole[t011], multipole[t002]}};
1261 }
1262
1263
1264
1265
1266 @Override
1267 public int hashCode() {
1268 return Arrays.hashCode(frameAtomTypes);
1269 }
1270
1271
1272
1273
1274 @Override
1275 public String toString() {
1276 return toBohrString();
1277 }
1278
1279
1280
1281
1282
1283
1284 void incrementType(int increment) {
1285 for (int i = 0; i < frameAtomTypes.length; i++) {
1286
1287 if (frameAtomTypes[i] > 0) {
1288 frameAtomTypes[i] += increment;
1289 } else if (frameAtomTypes[i] < 0) {
1290 frameAtomTypes[i] -= increment;
1291 }
1292 }
1293 setKey(frameAtomTypes);
1294 }
1295
1296
1297
1298
1299
1300
1301
1302 MultipoleType patchTypes(HashMap<AtomType, AtomType> typeMap) {
1303 int count = 0;
1304 int len = frameAtomTypes.length;
1305
1306
1307 for (AtomType newType : typeMap.keySet()) {
1308 for (int frameAtomType : frameAtomTypes) {
1309 if (frameAtomType == newType.type || frameAtomType == 0) {
1310 count++;
1311 }
1312 }
1313 }
1314
1315
1316 if (count > 0 && count < len) {
1317 int[] newFrame = Arrays.copyOf(frameAtomTypes, len);
1318 for (AtomType newType : typeMap.keySet()) {
1319 for (int i = 0; i < len; i++) {
1320 if (frameAtomTypes[i] == newType.type) {
1321 AtomType knownType = typeMap.get(newType);
1322 newFrame[i] = knownType.type;
1323 }
1324 }
1325 }
1326 return new MultipoleType(multipole, newFrame, frameDefinition, false);
1327 }
1328 return null;
1329 }
1330
1331 private void checkMultipole() {
1332 double[][] quadrupole = unpackQuad(multipole);
1333
1334 if (abs(quadrupole[0][1] - quadrupole[1][0]) > 1.0e-6) {
1335 logger.warning("Multipole component Qxy != Qyx");
1336 logger.info(this.toString());
1337 }
1338 if (abs(quadrupole[0][2] - quadrupole[2][0]) > 1.0e-6) {
1339 logger.warning("Multipole component Qxz != Qzx");
1340 logger.info(this.toString());
1341 }
1342 if (abs(quadrupole[1][2] - quadrupole[2][1]) > 1.0e-6) {
1343 logger.warning("Multipole component Qyz != Qzy");
1344 logger.info(this.toString());
1345 }
1346
1347 if (abs(quadrupole[0][0] + quadrupole[1][1] + quadrupole[2][2]) > 1.0e-5) {
1348 logger.log(Level.WARNING, format("Multipole is not traceless: %12.8f",
1349 abs(quadrupole[0][0] + quadrupole[1][1] + quadrupole[2][2])));
1350 logger.info(this.toString());
1351 }
1352 }
1353
1354
1355
1356
1357
1358
1359
1360 private String toBohrString() {
1361 StringBuilder multipoleBuffer = new StringBuilder("multipole");
1362 switch (frameDefinition) {
1363 case NONE -> multipoleBuffer.append(format(" %5d %5s %5s %5s", frameAtomTypes[0], "", "", ""));
1364 case ZONLY -> multipoleBuffer.append(
1365 format(" %5d %5d %5s %5s", frameAtomTypes[0], frameAtomTypes[1], "", ""));
1366 case ZTHENX -> {
1367 if (frameAtomTypes.length == 3) {
1368 multipoleBuffer.append(
1369 format(" %5d %5d %5d %5s", frameAtomTypes[0], frameAtomTypes[1], frameAtomTypes[2],
1370 ""));
1371 } else {
1372
1373 multipoleBuffer.append(
1374 format(" %5d %5d %5d %5d", frameAtomTypes[0], frameAtomTypes[1], frameAtomTypes[2],
1375 frameAtomTypes[3]));
1376 }
1377 }
1378 case BISECTOR -> multipoleBuffer.append(
1379 format(" %5d %5d %5d %5s", frameAtomTypes[0], -frameAtomTypes[1], -frameAtomTypes[2],
1380 ""));
1381 case ZTHENBISECTOR -> multipoleBuffer.append(
1382 format(" %5d %5d %5d %5d", frameAtomTypes[0], frameAtomTypes[1], -frameAtomTypes[2],
1383 -frameAtomTypes[3]));
1384 case THREEFOLD -> multipoleBuffer.append(
1385 format(" %5d %5d %5d %5d", frameAtomTypes[0], -frameAtomTypes[1], -frameAtomTypes[2],
1386 -frameAtomTypes[3]));
1387 }
1388 multipoleBuffer.append(format(
1389 " % 7.5f \\\n" + "%11$s % 7.5f % 7.5f % 7.5f \\\n" + "%11$s % 7.5f \\\n"
1390 + "%11$s % 7.5f % 7.5f \\\n" + "%11$s % 7.5f % 7.5f % 7.5f", multipole[t000],
1391 multipole[t100] / BOHR, multipole[t010] / BOHR, multipole[t001] / BOHR,
1392 multipole[t200] / BOHR2, multipole[t110] / BOHR2, multipole[t020] / BOHR2,
1393 multipole[t101] / BOHR2, multipole[t011] / BOHR2, multipole[t002] / BOHR2,
1394 " "));
1395 return multipoleBuffer.toString();
1396 }
1397
1398
1399
1400
1401 public enum MultipoleFrameDefinition {
1402 NONE, ZONLY, ZTHENX, BISECTOR, ZTHENBISECTOR, THREEFOLD,
1403 }
1404 }