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