View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
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   * The MultipoleType class defines a multipole in its local frame.
72   *
73   * @author Michael J. Schnieders
74   * @since 1.0
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     * Constant <code>zeroM</code>
92     */
93    public static final double[] zeroM = new double[10];
94    /**
95     * Constant <code>zeroD</code>
96     */
97    public static final double[] zeroD = new double[3];
98    /**
99     * Constant <code>zeroD</code>
100    */
101   public static final double[][] zeroQ = new double[3][3];
102   /**
103    * Constant <code>chrg=t000</code>
104    */
105   public static final int t000 = 0;
106   /**
107    * Constant <code>t100=1</code>
108    */
109   public static final int t100 = 1;
110   /**
111    * Constant <code>t010=2</code>
112    */
113   public static final int t010 = 2;
114   /**
115    * Constant <code>t001=3</code>
116    */
117   public static final int t001 = 3;
118   /**
119    * Constant <code>t200=4</code>
120    */
121   public static final int t200 = 4;
122   /**
123    * Constant <code>t020=5</code>
124    */
125   public static final int t020 = 5;
126   /**
127    * Constant <code>t002=6</code>
128    */
129   public static final int t002 = 6;
130   /**
131    * Constant <code>t110=7</code>
132    */
133   public static final int t110 = 7;
134   /**
135    * Constant <code>t101=8</code>
136    */
137   public static final int t101 = 8;
138   /**
139    * Constant <code>t011=9</code>
140    */
141   public static final int t011 = 9;
142   /**
143    * Constant <code>t300=10</code>
144    */
145   public static final int t300 = 10;
146   /**
147    * Constant <code>t030=11</code>
148    */
149   public static final int t030 = 11;
150   /**
151    * Constant <code>t003=12</code>
152    */
153   public static final int t003 = 12;
154   /**
155    * Constant <code>t210=13</code>
156    */
157   public static final int t210 = 13;
158   /**
159    * Constant <code>t201=14</code>
160    */
161   public static final int t201 = 14;
162   /**
163    * Constant <code>t120=15</code>
164    */
165   public static final int t120 = 15;
166   /**
167    * Constant <code>t021=16</code>
168    */
169   public static final int t021 = 16;
170   /**
171    * Constant <code>t102=17</code>
172    */
173   public static final int t102 = 17;
174   /**
175    * Constant <code>t012=18</code>
176    */
177   public static final int t012 = 18;
178   /**
179    * Constant <code>t111=19</code>
180    */
181   public static final int t111 = 19;
182 
183   private static final Logger logger = Logger.getLogger(MultipoleType.class.getName());
184   /**
185    * Partial atomic charge (e).
186    */
187   public final double charge;
188   /**
189    * Atomic dipole. 1 x 3 (e Angstroms).
190    */
191   public final double[] dipole;
192   /**
193    * Atomic quadrupole. 3 x 3 (e Angstroms^2).
194    */
195   public final double[][] quadrupole;
196   /**
197    * Local frame definition method.
198    */
199   public final MultipoleFrameDefinition frameDefinition;
200   /**
201    * Atom types that define the local frame of this multipole.
202    */
203   public final int[] frameAtomTypes;
204   /**
205    * Charge, dipole, and quadrupole packed into tensor notation: c, dx, dy, dz, qxx, qyy, qzz, qxy,
206    * qxz, qyz
207    */
208   private final double[] multipole;
209 
210   /**
211    * Multipole Constructor. Conversion to electron Angstroms should be requested only when reading
212    * multipole values from the force field file.
213    *
214    * @param multipole       an array of {@link double} objects.
215    * @param frameAtomTypes  an array of {@link int} objects.
216    * @param frameDefinition a
217    *                        {@link ffx.potential.parameters.MultipoleType.MultipoleFrameDefinition} object.
218    * @param convertFromBohr a boolean.
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    * Construct a MultipoleType from a reference type and updated coefficients.
234    *
235    * @param multipoleType Reference MultipoleType.
236    * @param multipole     The updated multipole parameters.
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    * Constructor for MultipoleType.
245    *
246    * @param charge          a double.
247    * @param dipole          an array of {@link double} objects.
248    * @param quadrupole      an array of {@link double} objects.
249    * @param frameAtomTypes  an array of {@link int} objects.
250    * @param frameDefinition a
251    *                        {@link ffx.potential.parameters.MultipoleType.MultipoleFrameDefinition} object.
252    * @param convertFromBohr a boolean.
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    * Assign the multipole type.
274    *
275    * @param elecForm   The electrostatics form.
276    * @param atom       The atom to assign.
277    * @param forceField The force field.
278    * @param multipole  Copy the multipole parameters to this array.
279    * @param i          the index of the atom.
280    * @param axisAtom   Store the axis atom indices into this array.
281    * @param frame      Store the frame definition into this array.
282    * @return True if the multipole type was assigned.
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    * Average two MultipoleType instances. The atom types that define the frame of the new type must
298    * be supplied.
299    *
300    * @param multipoleType1      a {@link ffx.potential.parameters.MultipoleType} object.
301    * @param multipoleType2      a {@link ffx.potential.parameters.MultipoleType} object.
302    * @param multipoleFrameTypes an array of {@link int} objects.
303    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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    * checkMultipoleChirality.
322    *
323    * @param frame       a {@link ffx.potential.parameters.MultipoleType.MultipoleFrameDefinition}
324    *                    object.
325    * @param frameCoords an array of {@link double} objects.
326    * @param localOrigin an array of {@link double} objects.
327    * @return Whether this multipole underwent chiral inversion.
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    * Return the rotation matrix for the local to lab frame.
359    *
360    * @param frame       the multipole frame definition
361    * @param frameCoords the coordinates of the frame atoms
362    * @param localOrigin the local origin of the frame
363    * @return the rotation matrix
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    * Return the rotation matrix for the local to lab frame.
374    *
375    * @param frame       the multipole frame definition
376    * @param frameCoords the coordinates of the frame atoms
377    * @param localOrigin the local origin of the frame
378    * @param rotMat      the rotation matrix.
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     // Use the identity matrix as the default rotation matrix
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         // Z-Axis
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         // Sum the normalized vectors to the bisector atoms.
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         // X-Axis: initially assume its along the global X-axis.
460         xAxis[0] = 1.0;
461         xAxis[1] = 0.0;
462         xAxis[2] = 0.0;
463         // If the Z-axis is close to the global X-axis,
464         // assume an X-axis along the global Y-axis.
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       // 3-Fold frame rotation matrix elements for Z- and X-axes.
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     // Set the X elements.
524     rotMat[0][0] = xAxis[0];
525     rotMat[1][0] = xAxis[1];
526     rotMat[2][0] = xAxis[2];
527     // Set the Y elements.
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    * multipoleTypeFactory.
535    *
536    * @param elecForm   The electrostatics form being used.
537    * @param atom       a {@link ffx.potential.bonded.Atom} object.
538    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
539    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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     // No reference atoms.
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     // List of sorted of 1-2 interactions.
576     List<Atom> n12 = atom.get12List();
577 
578     // No bonds.
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     // Only 1-2 connected atoms: 1 reference atom.
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     // Only 1-2 connected atoms: 2 reference atoms.
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     // Only 1-2 connected atoms: 3 reference atoms.
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     // Revert to a reference atom definition that may include a 1-3 site. For example a hydrogen on
641     // water.
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    * Assign local multipole frame defining atoms.
672    *
673    * @param atom Assign local multipole frame defining atoms for this atom.
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       // No reference atoms.
684       atom.setAxisAtoms((Atom[]) null);
685       return;
686     }
687 
688     // List sorted of 1-2 interactions.
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     // Only 1-2 connected atoms: 1 reference atom.
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     // Only 1-2 connected atoms: 2 reference atoms.
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     // Only 1-2 connected atoms: 3 reference atoms.
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     // Revert to a reference atom definition that may include a 1-3 site.
745     // For example a hydrogen on water.
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    * Parse a single line multipole.
774    *
775    * @param input  Input String.
776    * @param tokens Input tokens.
777    * @param br     A BufferedReader instance.
778    * @return a MultipoleType instance.
779    * @since 1.0
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       // Loop over integer tokens (i.e. except for the initial 'multipole' keyword and final charge
791       // value).
792       for (int i = 1; i < nTokens - 1; i++) {
793         int frameType = parseInt(tokens[i]);
794         // Ignore atom types of '0'.
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       // Last token is the monopole.
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         // ZTHENX or BISECTOR
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         // ZTHENBISECTOR or THREEFOLD
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       // Warn if the frame definition is ambiguous.
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       // Fill in symmetric components.
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    * Parse a single line multipole.
888    *
889    * @param input  Input String.
890    * @param tokens Input tokens.
891    * @return a MultipoleType instance.
892    * @since 1.0
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       // Loop over integer tokens (i.e. except for the initial 'multipole' keyword and final 10
904       // multipole values).
905       for (int i = 1; i < nTokens - 10; i++) {
906         int frameType = parseInt(tokens[i]);
907         // Ignore atom types of '0'.
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         // ZTHENX or BISECTOR
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         // ZTHENBISECTOR or THREEFOLD
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       // Warn if the frame definition is ambiguous.
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       // Fill in symmetric components.
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    * Map charge parameters to a Multipole instance.
976    *
977    * @param input  Input string.
978    * @param tokens Input string tokens.
979    * @return a MultipoleType instance
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    * rotateDipole.
1002    *
1003    * @param rotMat        an array of {@link double} objects.
1004    * @param dipole        an array of {@link double} objects.
1005    * @param rotatedDipole an array of {@link double} objects.
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    * rotateMultipole.
1018    *
1019    * @param rotmat            an array of {@link double} objects.
1020    * @param dipole            an array of {@link double} objects.
1021    * @param quadrupole        an array of {@link double} objects.
1022    * @param rotatedDipole     an array of {@link double} objects.
1023    * @param rotatedQuadrupole an array of {@link double} objects.
1024    */
1025   public static void rotateMultipole(double[][] rotmat, double[] dipole, double[][] quadrupole,
1026                                      double[] rotatedDipole, double[][] rotatedQuadrupole) {
1027 
1028     // Initialize the rotated multipole to zero.
1029     fill(rotatedDipole, 0.0);
1030     for (int i = 0; i < 3; i++) {
1031       fill(rotatedQuadrupole[i], 0.0);
1032     }
1033 
1034     // Compute the rotated multipole.
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    * scale.
1058    *
1059    * @param type      a {@link ffx.potential.parameters.MultipoleType} object.
1060    * @param cdtScales an array of {@link double} objects.
1061    * @return an array of {@link double} objects.
1062    */
1063   public static double[] scale(MultipoleType type, double[] cdtScales) {
1064     return scale(type.getMultipole(), cdtScales);
1065   }
1066 
1067   /**
1068    * scale.
1069    *
1070    * @param multipole an array of {@link double} objects.
1071    * @param cdtScales an array of {@link double} objects.
1072    * @return an array of {@link double} objects.
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   /* Indices into a 1D tensor array based on compressed tensor notation. This makes multipole code easier to read. */
1085 
1086   /**
1087    * weightMultipoleTypes.
1088    *
1089    * @param types          an array of {@link ffx.potential.parameters.MultipoleType} objects.
1090    * @param weights        an array of {@link double} objects.
1091    * @param frameAtomTypes an array of {@link int} objects.
1092    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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    * Pack charge, dipole, quad into 1d tensor array form.
1112    *
1113    * @param charge a double.
1114    * @param dipole an array of {@link double} objects.
1115    * @param quad   an array of {@link double} objects.
1116    * @return an array of {@link double} objects.
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    * Unpack dipole from 1d tensor-form multipole.
1125    */
1126   private static double[] unpackDipole(double[] mpole) {
1127     return new double[]{mpole[t100], mpole[t010], mpole[t001]};
1128   }
1129 
1130   /**
1131    * Unpack quadrupole from 1d tensor-form multipole.
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    * Create a new multipole representing a weighted average.
1140    *
1141    * @param types   an array of {@link ffx.potential.parameters.MultipoleType} objects.
1142    * @param weights an array of {@link double} objects.
1143    * @return an array of {@link double} objects.
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       // Multipoles have not yet been assigned.
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    * {@inheritDoc}
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    * {@inheritDoc}
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    * Getter for the field <code>charge</code>.
1223    *
1224    * @return An uneditable copy of this type's charge. To make changes, use getMultipoleReference().
1225    */
1226   public double getCharge() {
1227     return multipole[t000];
1228   }
1229 
1230   /**
1231    * Getter for the field <code>dipole</code>.
1232    *
1233    * @return An uneditable copy of this type's dipole. To make changes, use getMultipoleReference().
1234    */
1235   public double[] getDipole() {
1236     return new double[]{multipole[t100], multipole[t010], multipole[t001]};
1237   }
1238 
1239   /**
1240    * Getter for the field <code>multipole</code>.
1241    *
1242    * @return An uneditable copy of this type's multipole. To make changes, use
1243    * getMultipoleReference().
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    * Getter for the field <code>quadrupole</code>.
1253    *
1254    * @return An uneditable copy of this type's quadrupole. To make changes, use
1255    * getMultipoleReference().
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    * {@inheritDoc}
1265    */
1266   @Override
1267   public int hashCode() {
1268     return Arrays.hashCode(frameAtomTypes);
1269   }
1270 
1271   /**
1272    * {@inheritDoc}
1273    */
1274   @Override
1275   public String toString() {
1276     return toBohrString();
1277   }
1278 
1279   /**
1280    * incrementType
1281    *
1282    * @param increment The amount to increment the frame atom types by.
1283    */
1284   void incrementType(int increment) {
1285     for (int i = 0; i < frameAtomTypes.length; i++) {
1286       // Frame atom types of 0 are unchanged.
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    * Remap new atom types to known internal ones.
1298    *
1299    * @param typeMap a lookup between new atom types and known atom types.
1300    * @return a {@link ffx.potential.parameters.MultipoleType} object.
1301    */
1302   MultipoleType patchTypes(HashMap<AtomType, AtomType> typeMap) {
1303     int count = 0;
1304     int len = frameAtomTypes.length;
1305 
1306     // Look for a MultipoleType that contain a mapped atom class.
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     // If found, create a new MultipoleType that bridges to known classes.
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     // Check symmetry.
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     // Warn if the multipole is not traceless.
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    * Nicely formatted multipole string. Dipole and quadrupole are in electron-Bohr and
1356    * electron-Bohr^2, respectively.
1357    *
1358    * @return String
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           // Chiral
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    * The local multipole frame is defined by the Z-then-X or Bisector convention.
1400    */
1401   public enum MultipoleFrameDefinition {
1402     NONE, ZONLY, ZTHENX, BISECTOR, ZTHENBISECTOR, THREEFOLD,
1403   }
1404 }