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.numerics.multipole;
39  
40  import static ffx.numerics.math.DoubleMath.dot;
41  import static ffx.numerics.math.DoubleMath.normalize;
42  import static ffx.numerics.math.DoubleMath.scale;
43  import static ffx.numerics.math.DoubleMath.sub;
44  import static java.util.Arrays.copyOf;
45  
46  /**
47   * The QIFrame class defines a quasi-internal frame between two atoms.
48   * <p>
49   * The Z-axis of the QI frame is defined as the vector between the two atoms. The X- and Y-axes are
50   * then defined to create a right-handed coordinate system.
51   * <p>
52   * A rotation matrix from the global frame to the QI frame is constructed, and vice versa. Using the
53   * rotation matrices, methods are provided to rotate vectors and multipoles between the two frames.
54   *
55   * @author Michael J. Schnieders
56   * @since 1.0
57   */
58  public class QIFrame {
59  
60    // Rotation Matrix from Global to QI.
61    private double r00, r01, r02;
62    private double r10, r11, r12;
63    private double r20, r21, r22;
64  
65    // Rotation Matrix from QI to Global.
66    private double ir00, ir01, ir02;
67    private double ir10, ir11, ir12;
68    private double ir20, ir21, ir22;
69  
70    /**
71     * QIFrame constructor
72     * <p>
73     * (dx = 0, dy = 0, dz = 1).
74     */
75    public QIFrame() {
76      setQIVector(0.0, 0.0, 1.0);
77    }
78  
79    /**
80     * QIFrame constructor.
81     *
82     * @param dx Separation along the x-axis.
83     * @param dy Separation along the y-axis.
84     * @param dz Separation along the z-axis.
85     */
86    public QIFrame(double dx, double dy, double dz) {
87      setQIVector(dx, dy, dz);
88    }
89  
90    /**
91     * QIFrame constructor.
92     *
93     * @param r Separation along each axis.
94     */
95    public QIFrame(double[] r) {
96      setQIVector(r[0], r[1], r[2]);
97    }
98  
99    /**
100    * Update the QIFrame rotation matrix.
101    *
102    * @param r Separation along each axis.
103    */
104   public void setQIVector(double[] r) {
105     setQIVector(r[0], r[1], r[2]);
106   }
107 
108   /**
109    * Update the QIFrame rotation matrix.
110    *
111    * @param dx Separation along the x-axis.
112    * @param dy Separation along the y-axis.
113    * @param dz Separation along the z-axis.
114    */
115   public final void setQIVector(double dx, double dy, double dz) {
116     // The QI Z-axis is along the separation vector.
117     double[] zAxis = {dx, dy, dz};
118 
119     // The "guess" for the QI X-axis cannot be along the QI separation vector.
120     double[] xAxis = copyOf(zAxis, 3);
121     if (dy != 0.0 || dz != 0.0) {
122       // The QI separation vector is not along the global X-axis.
123       // Set the QI X-axis "guess" to the QI Z-axis plus add 1 to X-component.
124       xAxis[0] += 1.0;
125     } else {
126       // The QI separation vector is the global X-axis.
127       // Set the QI X-axis "guess" to the QI Z-axis plus add 1 to Y-component.
128       xAxis[1] += 1.0;
129     }
130 
131     // Normalize the QI Z-axis.
132     normalize(zAxis, zAxis);
133     ir02 = zAxis[0];
134     ir12 = zAxis[1];
135     ir22 = zAxis[2];
136 
137     // Finalize the QI X-axis.
138     double dot = dot(xAxis, zAxis);
139     scale(zAxis, dot, zAxis);
140     sub(xAxis, zAxis, xAxis);
141     normalize(xAxis, xAxis);
142     ir00 = xAxis[0];
143     ir10 = xAxis[1];
144     ir20 = xAxis[2];
145 
146     // Cross the QI X-axis and Z-axis to get the QI Y-axis.
147     ir01 = ir20 * ir12 - ir10 * ir22;
148     ir11 = ir00 * ir22 - ir20 * ir02;
149     ir21 = ir10 * ir02 - ir00 * ir12;
150 
151     // Set the forward elements as the transpose of the inverse matrix.
152     r00 = ir00;
153     r11 = ir11;
154     r22 = ir22;
155     r01 = ir10;
156     r02 = ir20;
157     r10 = ir01;
158     r12 = ir21;
159     r20 = ir02;
160     r21 = ir12;
161   }
162 
163   /**
164    * Update the QIFrame rotation matrix and rotate the multipoles.
165    *
166    * @param r Separation along each axis.
167    * @param mI PolarizableMultipole for site I.
168    * @param mK PolarizableMultipole for site K.
169    */
170   public void setAndRotate(double[] r, PolarizableMultipole mI, PolarizableMultipole mK) {
171     setAndRotate(r[0], r[1], r[2], mI, mK);
172   }
173 
174   /**
175    * Update the QIFrame rotation matrix and rotate the multipoles.
176    *
177    * @param dx Separation along the x-axis.
178    * @param dy Separation along the y-axis.
179    * @param dz Separation along the z-axis.
180    * @param mI PolarizableMultipole for site I.
181    * @param mK PolarizableMultipole for site K.
182    */
183   public void setAndRotate(double dx, double dy, double dz,
184       PolarizableMultipole mI, PolarizableMultipole mK) {
185     setQIVector(dx, dy, dz);
186     rotatePolarizableMultipole(mI);
187     rotatePolarizableMultipole(mK);
188   }
189 
190   /**
191    * Rotate the permanent multipole and induced dipole.
192    *
193    * @param m PolarizableMultipole to rotate.
194    */
195   public void rotatePolarizableMultipole(PolarizableMultipole m) {
196     rotatePermanentMultipole(m);
197     rotateInducedDipoles(m);
198   }
199 
200   /**
201    * Rotate the permanent multipole.
202    *
203    * @param m PolarizableMultipole to rotate.
204    */
205   public void rotatePermanentMultipole(PolarizableMultipole m) {
206     // Rotate the permanent dipole.
207     double dx = m.dx;
208     double dy = m.dy;
209     double dz = m.dz;
210     m.dx = r00 * dx + r01 * dy + r02 * dz;
211     m.dy = r10 * dx + r11 * dy + r12 * dz;
212     m.dz = r20 * dx + r21 * dy + r22 * dz;
213 
214     // Rotate the quadrupole.
215     double qxx = m.qxx;
216     double qyy = m.qyy;
217     double qzz = m.qzz;
218     // The Multipole class stores 2.0 times the off-diagonal components.
219     double qxy = m.qxy * 0.5;
220     double qxz = m.qxz * 0.5;
221     double qyz = m.qyz * 0.5;
222 
223     m.qxx = r00 * (r00 * qxx + r01 * qxy + r02 * qxz)
224         + r01 * (r00 * qxy + r01 * qyy + r02 * qyz)
225         + r02 * (r00 * qxz + r01 * qyz + r02 * qzz);
226 
227     m.qxy = r00 * (r10 * qxx + r11 * qxy + r12 * qxz)
228         + r01 * (r10 * qxy + r11 * qyy + r12 * qyz)
229         + r02 * (r10 * qxz + r11 * qyz + r12 * qzz);
230     m.qxy *= 2.0;
231 
232     m.qxz = r00 * (r20 * qxx + r21 * qxy + r22 * qxz)
233         + r01 * (r20 * qxy + r21 * qyy + r22 * qyz)
234         + r02 * (r20 * qxz + r21 * qyz + r22 * qzz);
235     m.qxz *= 2.0;
236 
237     m.qyy = r10 * (r10 * qxx + r11 * qxy + r12 * qxz)
238         + r11 * (r10 * qxy + r11 * qyy + r12 * qyz)
239         + r12 * (r10 * qxz + r11 * qyz + r12 * qzz);
240 
241     m.qyz = r10 * (r20 * qxx + r21 * qxy + r22 * qxz)
242         + r11 * (r20 * qxy + r21 * qyy + r22 * qyz)
243         + r12 * (r20 * qxz + r21 * qyz + r22 * qzz);
244     m.qyz *= 2.0;
245 
246     m.qzz = r20 * (r20 * qxx + r21 * qxy + r22 * qxz)
247         + r21 * (r20 * qxy + r21 * qyy + r22 * qyz)
248         + r22 * (r20 * qxz + r21 * qyz + r22 * qzz);
249   }
250 
251   /**
252    * Rotate the induced dipoles components.
253    *
254    * @param m PolarizableMultipole to rotate.
255    */
256   public void rotateInducedDipoles(PolarizableMultipole m) {
257     double dx = m.ux;
258     double dy = m.uy;
259     double dz = m.uz;
260     m.ux = r00 * dx + r01 * dy + r02 * dz;
261     m.uy = r10 * dx + r11 * dy + r12 * dz;
262     m.uz = r20 * dx + r21 * dy + r22 * dz;
263     dx = m.px;
264     dy = m.py;
265     dz = m.pz;
266     m.px = r00 * dx + r01 * dy + r02 * dz;
267     m.py = r10 * dx + r11 * dy + r12 * dz;
268     m.pz = r20 * dx + r21 * dy + r22 * dz;
269     // Set update the averaged induced and chain rule terms.
270     m.sx = (m.ux + m.px) * 0.5;
271     m.sy = (m.uy + m.py) * 0.5;
272     m.sz = (m.uz + m.pz) * 0.5;
273   }
274 
275   /**
276    * Rotate a vector in the QI frame into the global frame.
277    *
278    * @param v The vector to rotate (in-place).
279    */
280   public void toGlobal(double[] v) {
281     double vx = v[0];
282     double vy = v[1];
283     double vz = v[2];
284     // X-component.
285     v[0] = ir00 * vx + ir01 * vy + ir02 * vz;
286     // Y-component.
287     v[1] = ir10 * vx + ir11 * vy + ir12 * vz;
288     // Z-component.
289     v[2] = ir20 * vx + ir21 * vy + ir22 * vz;
290   }
291 }