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-2026.
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 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   * The MultipoleType class defines a multipole in its local frame.
76   *
77   * @author Michael J. Schnieders
78   * @since 1.0
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     * Constant <code>zeroM</code>
96     */
97    public static final double[] zeroM = new double[10];
98    /**
99     * Constant <code>zeroD</code>
100    */
101   public static final double[] zeroD = new double[3];
102   /**
103    * Constant <code>zeroD</code>
104    */
105   public static final double[][] zeroQ = new double[3][3];
106   /**
107    * Constant <code>chrg=t000</code>
108    */
109   public static final int t000 = 0;
110   /**
111    * Constant <code>t100=1</code>
112    */
113   public static final int t100 = 1;
114   /**
115    * Constant <code>t010=2</code>
116    */
117   public static final int t010 = 2;
118   /**
119    * Constant <code>t001=3</code>
120    */
121   public static final int t001 = 3;
122   /**
123    * Constant <code>t200=4</code>
124    */
125   public static final int t200 = 4;
126   /**
127    * Constant <code>t020=5</code>
128    */
129   public static final int t020 = 5;
130   /**
131    * Constant <code>t002=6</code>
132    */
133   public static final int t002 = 6;
134   /**
135    * Constant <code>t110=7</code>
136    */
137   public static final int t110 = 7;
138   /**
139    * Constant <code>t101=8</code>
140    */
141   public static final int t101 = 8;
142   /**
143    * Constant <code>t011=9</code>
144    */
145   public static final int t011 = 9;
146   /**
147    * Constant <code>t300=10</code>
148    */
149   public static final int t300 = 10;
150   /**
151    * Constant <code>t030=11</code>
152    */
153   public static final int t030 = 11;
154   /**
155    * Constant <code>t003=12</code>
156    */
157   public static final int t003 = 12;
158   /**
159    * Constant <code>t210=13</code>
160    */
161   public static final int t210 = 13;
162   /**
163    * Constant <code>t201=14</code>
164    */
165   public static final int t201 = 14;
166   /**
167    * Constant <code>t120=15</code>
168    */
169   public static final int t120 = 15;
170   /**
171    * Constant <code>t021=16</code>
172    */
173   public static final int t021 = 16;
174   /**
175    * Constant <code>t102=17</code>
176    */
177   public static final int t102 = 17;
178   /**
179    * Constant <code>t012=18</code>
180    */
181   public static final int t012 = 18;
182   /**
183    * Constant <code>t111=19</code>
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    * Partial atomic charge (e).
194    */
195   public final double charge;
196   /**
197    * Atomic dipole. 1 x 3 (e Angstroms).
198    */
199   public final double[] dipole;
200   /**
201    * Atomic quadrupole. 3 x 3 (e Angstroms^2).
202    */
203   public final double[][] quadrupole;
204   /**
205    * Local frame definition method.
206    */
207   public final MultipoleFrameDefinition frameDefinition;
208   /**
209    * Atom types that define the local frame of this multipole.
210    */
211   public final int[] frameAtomTypes;
212   /**
213    * Charge, dipole, and quadrupole packed into tensor notation: c, dx, dy, dz, qxx, qyy, qzz, qxy,
214    * qxz, qyz
215    */
216   private final double[] multipole;
217 
218   /**
219    * Multipole Constructor. Conversion to electron Angstroms should be requested only when reading
220    * multipole values from the force field file.
221    *
222    * @param multipole       the multipole parameters.
223    * @param frameAtomTypes  the atom types that define the local frame of this multipole.
224    * @param frameDefinition the local frame definition method.
225    * @param convertFromBohr a boolean.
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    * Construct a MultipoleType from a reference type and updated coefficients.
241    *
242    * @param multipoleType Reference MultipoleType.
243    * @param multipole     The updated multipole parameters.
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    * Constructor for MultipoleType.
252    *
253    * @param charge          the atomic charge in electrons.
254    * @param dipole          the atomic dipole.
255    * @param quadrupole      the atomic quadrupole.
256    * @param frameAtomTypes  the atom types that define the local frame of this multipole.
257    * @param frameDefinition the local frame definition method.
258    * @param convertFromBohr a boolean.
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    * Assign the multipole type.
280    *
281    * @param elecForm   The electrostatics form.
282    * @param atom       The atom to assign.
283    * @param forceField The force field.
284    * @param multipole  Copy the multipole parameters to this array.
285    * @param i          the index of the atom.
286    * @param axisAtom   Store the axis atom indices into this array.
287    * @param frame      Store the frame definition into this array.
288    * @return True if the multipole type was assigned.
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    * Average two MultipoleType instances. The atom types that define the frame of the new type must
304    * be supplied.
305    *
306    * @param multipoleType1      the first {@link ffx.potential.parameters.MultipoleType} object.
307    * @param multipoleType2      the second {@link ffx.potential.parameters.MultipoleType} object.
308    * @param multipoleFrameTypes the atom types that define the local frame of the new multipole type.
309    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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    * checkMultipoleChirality.
328    *
329    * @param frame       the multipole frame definition.
330    * @param localOrigin the local origin of the multipole frame.
331    * @param frameCoords the coordinates of the frame atoms.
332    * @return Whether this multipole underwent chiral inversion.
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    * Return the rotation matrix for the local to lab frame.
364    *
365    * @param frame       the multipole frame definition
366    * @param frameCoords the coordinates of the frame atoms
367    * @param localOrigin the local origin of the frame
368    * @return the rotation matrix
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    * Return the rotation matrix for the local to lab frame.
379    *
380    * @param frame       the multipole frame definition
381    * @param frameCoords the coordinates of the frame atoms
382    * @param localOrigin the local origin of the frame
383    * @param rotMat      the rotation matrix.
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     // Use the identity matrix as the default rotation matrix
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         // Z-Axis
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         // Sum the normalized vectors to the bisector atoms.
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         // X-Axis: initially assume its along the global X-axis.
465         xAxis[0] = 1.0;
466         xAxis[1] = 0.0;
467         xAxis[2] = 0.0;
468         // If the Z-axis is close to the global X-axis,
469         // assume an X-axis along the global Y-axis.
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       // 3-Fold frame rotation matrix elements for Z- and X-axes.
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     // Set the X elements.
530     rotMat[0][0] = xAxis[0];
531     rotMat[1][0] = xAxis[1];
532     rotMat[2][0] = xAxis[2];
533     // Set the Y elements.
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    * multipoleTypeFactory.
541    *
542    * @param elecForm   The electrostatics form being used.
543    * @param atom       a {@link ffx.potential.bonded.Atom} object.
544    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
545    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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     // No reference atoms.
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     // List of sorted of 1-2 interactions.
582     List<Atom> n12 = atom.get12List();
583 
584     // No bonds.
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     // Only 1-2 connected atoms: 1 reference atom.
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     // Only 1-2 connected atoms: 2 reference atoms.
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     // Only 1-2 connected atoms: 3 reference atoms.
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     // Revert to a reference atom definition that may include a 1-3 site. For example a hydrogen on
647     // water.
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    * Assign local multipole frame defining atoms.
678    *
679    * @param atom Assign local multipole frame defining atoms for this atom.
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       // No reference atoms.
690       atom.setAxisAtoms((Atom[]) null);
691       return;
692     }
693 
694     // List sorted of 1-2 interactions.
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     // Only 1-2 connected atoms: 1 reference atom.
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     // Only 1-2 connected atoms: 2 reference atoms.
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     // Only 1-2 connected atoms: 3 reference atoms.
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     // Revert to a reference atom definition that may include a 1-3 site.
751     // For example a hydrogen on water.
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    * Parse a single line multipole.
780    *
781    * @param input  Input String.
782    * @param tokens Input tokens.
783    * @param br     A BufferedReader instance.
784    * @return a MultipoleType instance.
785    * @since 1.0
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       // Loop over integer tokens (i.e. except for the initial 'multipole' keyword and final charge
797       // value).
798       for (int i = 1; i < nTokens - 1; i++) {
799         int frameType = parseInt(tokens[i]);
800         // Ignore atom types of '0'.
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       // Last token is the monopole.
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         // ZTHENX or BISECTOR
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         // ZTHENBISECTOR or THREEFOLD
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       // Warn if the frame definition is ambiguous.
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       // Fill in symmetric components.
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    * Parse a single line multipole.
894    *
895    * @param input  Input String.
896    * @param tokens Input tokens.
897    * @return a MultipoleType instance.
898    * @since 1.0
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       // Loop over integer tokens (i.e. except for the initial 'multipole' keyword and final 10
910       // multipole values).
911       for (int i = 1; i < nTokens - 10; i++) {
912         int frameType = parseInt(tokens[i]);
913         // Ignore atom types of '0'.
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         // ZTHENX or BISECTOR
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         // ZTHENBISECTOR or THREEFOLD
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       // Warn if the frame definition is ambiguous.
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       // Fill in symmetric components.
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    * Map charge parameters to a Multipole instance.
982    *
983    * @param input  Input string.
984    * @param tokens Input string tokens.
985    * @return a MultipoleType instance
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    * Rotate a dipole vector using a rotation matrix.
1008    *
1009    * @param rotMat        the rotation matrix.
1010    * @param dipole        the dipole to rotate.
1011    * @param rotatedDipole the rotated dipole.
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    * Rotate a multipole using a rotation matrix.
1024    *
1025    * @param rotmat            the rotation matrix.
1026    * @param dipole            the dipole to rotate.
1027    * @param quadrupole        the quadrupole to rotate.
1028    * @param rotatedDipole     the rotated dipole.
1029    * @param rotatedQuadrupole the rotated quadrupole.
1030    */
1031   public static void rotateMultipole(double[][] rotmat, double[] dipole, double[][] quadrupole,
1032                                      double[] rotatedDipole, double[][] rotatedQuadrupole) {
1033 
1034     // Initialize the rotated multipole to zero.
1035     fill(rotatedDipole, 0.0);
1036     for (int i = 0; i < 3; i++) {
1037       fill(rotatedQuadrupole[i], 0.0);
1038     }
1039 
1040     // Compute the rotated multipole.
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    * Scale a multipole by the charge, dipole, and quadrupole scales.
1064    *
1065    * @param type         a {@link ffx.potential.parameters.MultipoleType} object.
1066    * @param scaleFactors the charge, dipole, and quadrupole scales.
1067    * @return the scaled multipole.
1068    */
1069   public static double[] scale(MultipoleType type, double[] scaleFactors) {
1070     return scale(type.getMultipole(), scaleFactors);
1071   }
1072 
1073   /**
1074    * Scale a multipole by the charge, dipole, and quadrupole scales.
1075    *
1076    * @param multipole    the multipole to scale.
1077    * @param scaleFactors the charge, dipole, and quadrupole scales.
1078    * @return the scaled multipole.
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    * Create a new multipole representing a weighted average. The frame definition of the
1092    * MultipoleType is set to the frame definition of the first multipole type in the
1093    * types array.
1094    *
1095    * @param types          an array of {@link ffx.potential.parameters.MultipoleType} objects.
1096    * @param weights        the weights for each multipole type.
1097    * @param frameAtomTypes the atom types defining the multipole frame.
1098    * @return a {@link ffx.potential.parameters.MultipoleType} object.
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    * Convert a multipole from Bohr to electron-angstroms.
1111    *
1112    * @param multipole the multipole to convert.
1113    * @return a double array containing the multipole in electron-angstroms.
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    * Pack charge, dipole, quad into 1d tensor array form.
1124    *
1125    * @param charge a double.
1126    * @param dipole an array of {@link double} objects.
1127    * @param quad   an array of {@link double} objects.
1128    * @return an array of {@link double} objects.
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    * Unpack dipole from 1d tensor-form multipole.
1137    */
1138   private static double[] unpackDipole(double[] mpole) {
1139     return new double[]{mpole[t100], mpole[t010], mpole[t001]};
1140   }
1141 
1142   /**
1143    * Unpack quadrupole from 1d tensor-form multipole.
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    * Create a new multipole representing a weighted average.
1152    *
1153    * @param types   an array of {@link ffx.potential.parameters.MultipoleType} objects.
1154    * @param weights an array of {@link double} objects.
1155    * @return an array of {@link double} objects.
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       // Multipoles have not yet been assigned.
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    * {@inheritDoc}
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    * {@inheritDoc}
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    * Getter for the field <code>charge</code>.
1235    *
1236    * @return An uneditable copy of this type's charge. To make changes, use getMultipoleReference().
1237    */
1238   public double getCharge() {
1239     return multipole[t000];
1240   }
1241 
1242   /**
1243    * Getter for the field <code>dipole</code>.
1244    *
1245    * @return An uneditable copy of this type's dipole. To make changes, use getMultipoleReference().
1246    */
1247   public double[] getDipole() {
1248     return new double[]{multipole[t100], multipole[t010], multipole[t001]};
1249   }
1250 
1251   /**
1252    * Getter for the field <code>multipole</code>.
1253    *
1254    * @return An uneditable copy of this type's multipole. To make changes, use
1255    * getMultipoleReference().
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    * Getter for the field <code>quadrupole</code>.
1265    *
1266    * @return An uneditable copy of this type's quadrupole. To make changes, use
1267    * getMultipoleReference().
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    * {@inheritDoc}
1277    */
1278   @Override
1279   public int hashCode() {
1280     return Arrays.hashCode(frameAtomTypes);
1281   }
1282 
1283   /**
1284    * {@inheritDoc}
1285    */
1286   @Override
1287   public String toString() {
1288     return toBohrString();
1289   }
1290 
1291   /**
1292    * incrementType
1293    *
1294    * @param increment The amount to increment the frame atom types by.
1295    */
1296   void incrementType(int increment) {
1297     for (int i = 0; i < frameAtomTypes.length; i++) {
1298       // Frame atom types of 0 are unchanged.
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    * Remap new atom types to known internal ones.
1310    *
1311    * @param typeMap a lookup between new atom types and known atom types.
1312    * @return a {@link ffx.potential.parameters.MultipoleType} object.
1313    */
1314   MultipoleType patchTypes(HashMap<AtomType, AtomType> typeMap) {
1315     int count = 0;
1316     int len = frameAtomTypes.length;
1317 
1318     // Look for a MultipoleType that contain a mapped atom class.
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     // If found, create a new MultipoleType that bridges to known classes.
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     // Check symmetry.
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     // Warn if the multipole is not traceless.
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    * Nicely formatted multipole string. Dipole and quadrupole are in electron-Bohr and
1368    * electron-Bohr^2, respectively.
1369    *
1370    * @return String
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           // Chiral
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    * Create an AmoebaMultipoleForce Element.
1412    *
1413    * @param doc        the Document instance.
1414    * @param forceField the ForceField used to define constants.
1415    * @return the element.
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       // Add PolarizeType attributes
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    * Write MultipoleType to OpenMM XML format.
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           // Chiral
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     // FFX had dipoles in units of electrons * Angstrom.
1483     // OpenMM expects dipole units to be electrons * nm.
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     // FFX had quadrupoles in units of electrons * Angstrom^2.
1488     // OpenMM expects quadrupoles units to be electrons * nm^2.
1489     // FFX and Tinker follow the notation in the "Theory of Intermolecular Forces"
1490     // by Anthony Stone and apply the factor of 1/3 within their respective energy routines.
1491     // See Chapter 3 (e.g., Eq 3.1.3).
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    * The local multipole frame is defined by the Z-then-X or Bisector convention.
1503    */
1504   public enum MultipoleFrameDefinition {
1505     NONE, ZONLY, ZTHENX, BISECTOR, ZTHENBISECTOR, THREEFOLD,
1506   }
1507 }