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-2025.
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 jdk.incubator.vector.DoubleVector;
41  import jdk.incubator.vector.VectorMask;
42  
43  import static java.util.Arrays.copyOf;
44  
45  /**
46   * The QIFrame class defines a quasi-internal frame between two atoms.
47   * <p>
48   * The Z-axis of the QI frame is defined as the vector between the two atoms. The X- and Y-axes are
49   * then defined to create a right-handed coordinate system.
50   * <p>
51   * A rotation matrix from the global frame to the QI frame is constructed, and vice versa. Using the
52   * rotation matrices, methods are provided to rotate vectors and multipoles between the two frames.
53   *
54   * @author Michael J. Schnieders
55   * @since 1.0
56   */
57  public class QIFrameSIMD {
58  
59    // Rotation Matrix from Global to QI.
60    private DoubleVector r00Vec, r01Vec, r02Vec;
61    private DoubleVector r10Vec, r11Vec, r12Vec;
62    private DoubleVector r20Vec, r21Vec, r22Vec;
63  
64    // Rotation Matrix from QI to Global.
65    private DoubleVector ir00Vec, ir01Vec, ir02Vec;
66    private DoubleVector ir10Vec, ir11Vec, ir12Vec;
67    private DoubleVector ir20Vec, ir21Vec, ir22Vec;
68  
69    /**
70     * QIFrame constructor
71     * <p>
72     * (dx = 0, dy = 0, dz = 1).
73     */
74    public QIFrameSIMD() {
75      DoubleVector zero = DoubleVector.broadcast(DoubleVector.SPECIES_PREFERRED, 0.0);
76      setQIVector(zero, zero, zero);
77    }
78  
79    /**
80     * QIFrame constructor.
81     */
82    public QIFrameSIMD(DoubleVector dx, DoubleVector dy, DoubleVector dz) {
83      setQIVector(dx, dy, dz);
84    }
85  
86    /**
87     * QIFrame constructor.
88     *
89     * @param r Separation along each axis.
90     */
91    public QIFrameSIMD(DoubleVector[] r) {
92      setQIVector(r[0], r[1], r[2]);
93    }
94  
95    /**
96     * Update the QIFrame rotation matrix.
97     *
98     * @param r Separation along each axis.
99     */
100   public void setQIVector(DoubleVector[] r) {
101     setQIVector(r[0], r[1], r[2]);
102   }
103 
104   /**
105    * Update the QIFrame rotation matrix.
106    *
107    * @param dx Separation along the x-axis.
108    * @param dy Separation along the y-axis.
109    * @param dz Separation along the z-axis.
110    */
111   public final void setQIVector(DoubleVector dx, DoubleVector dy, DoubleVector dz) {
112     // The QI Z-axis is along the separation vector.
113     DoubleVector[] zAxis = {dx, dy, dz};
114 
115     // The "guess" for the QI X-axis cannot be along the QI separation vector.
116     DoubleVector[] xAxis = copyOf(zAxis, 3);
117 
118     // Check if the QI separation vector is the global X-axis.
119     VectorMask<Double> qiVectorIsXAxis = dz.eq(0.0).and(dy.eq(0.0));
120     // If the QI separation vector is the global X-axis, add 1 to the Y-component of the QI X-axis.
121     xAxis[1] = xAxis[1].add(1.0, qiVectorIsXAxis);
122     // Otherwise, add 1 to the X-component of the QI X-axis.
123     xAxis[0] = xAxis[0].add(1.0, qiVectorIsXAxis.not());
124 
125     // Normalize the QI Z-axis.
126     normalizeVec(zAxis, zAxis);
127     ir02Vec = zAxis[0];
128     ir12Vec = zAxis[1];
129     ir22Vec = zAxis[2];
130 
131     // Finalize the QI X-axis.
132     DoubleVector dot = dotVec(xAxis, zAxis);
133     scaleVec(zAxis, dot, zAxis);
134     subVec(xAxis, zAxis, xAxis);
135     normalizeVec(xAxis, xAxis);
136     ir00Vec = xAxis[0];
137     ir10Vec = xAxis[1];
138     ir20Vec = xAxis[2];
139 
140     // Cross the QI X-axis and Z-axis to get the QI Y-axis.
141     ir01Vec = ir20Vec.mul(ir12Vec).sub(ir10Vec.mul(ir22Vec));
142     ir11Vec = ir00Vec.mul(ir22Vec).sub(ir20Vec.mul(ir02Vec));
143     ir21Vec = ir10Vec.mul(ir02Vec).sub(ir00Vec.mul(ir12Vec));
144 
145     // Set the forward elements as the transpose of the inverse matrix.
146     r00Vec = ir00Vec;
147     r11Vec = ir11Vec;
148     r22Vec = ir22Vec;
149     r01Vec = ir10Vec;
150     r02Vec = ir20Vec;
151     r10Vec = ir01Vec;
152     r12Vec = ir21Vec;
153     r20Vec = ir02Vec;
154     r21Vec = ir12Vec;
155   }
156 
157   /**
158    * Compute the dot product of two vectors.
159    *
160    * @param a The first vector.
161    * @param b The second vector.
162    * @return The dot product of the two vectors.
163    */
164   public static DoubleVector dotVec(DoubleVector[] a, DoubleVector[] b) {
165     return a[0].fma(b[0], a[1].fma(b[1], a[2].mul(b[2])));
166   }
167 
168   /**
169    * Scale a vector by a scalar.
170    *
171    * @param a     The vector to scale.
172    * @param scale The scalar to multiply the vector by.
173    * @param ret   The vector to store the result in.
174    * @return The scaled vector.
175    */
176   public static DoubleVector[] scaleVec(DoubleVector[] a, DoubleVector scale, DoubleVector[] ret) {
177     ret[0] = a[0].mul(scale);
178     ret[1] = a[1].mul(scale);
179     ret[2] = a[2].mul(scale);
180     return ret;
181   }
182 
183   /**
184    * Subtract two vectors.
185    *
186    * @param a   The first vector.
187    * @param b   The second vector.
188    * @param ret The vector to store the result in.
189    * @return The difference of the two vectors.
190    */
191   public static DoubleVector[] subVec(DoubleVector[] a, DoubleVector[] b, DoubleVector[] ret) {
192     ret[0] = a[0].sub(b[0]);
193     ret[1] = a[1].sub(b[1]);
194     ret[2] = a[2].sub(b[2]);
195     return ret;
196   }
197 
198   /**
199    * Normalize a vector.
200    *
201    * @param n   The vector to normalize.
202    * @param ret The vector to store the result in.
203    * @return The normalized vector.
204    */
205   public static DoubleVector[] normalizeVec(DoubleVector[] n, DoubleVector[] ret) {
206     return scaleVec(n, DoubleVector.broadcast(DoubleVector.SPECIES_PREFERRED, 1.0).div(dotVec(n, n).sqrt()), ret);
207   }
208 
209   /**
210    * Update the QIFrame rotation matrix and rotate the multipoles.
211    *
212    * @param r  Separation along each axis.
213    * @param mI PolarizableMultipole for site I.
214    * @param mK PolarizableMultipole for site K.
215    */
216   public void setAndRotate(DoubleVector[] r, PolarizableMultipoleSIMD mI, PolarizableMultipoleSIMD mK) {
217     setAndRotate(r[0], r[1], r[2], mI, mK);
218   }
219 
220   /**
221    * Update the QIFrame rotation matrix and rotate the multipoles.
222    *
223    * @param dx Separation along the x-axis.
224    * @param dy Separation along the y-axis.
225    * @param dz Separation along the z-axis.
226    * @param mI PolarizableMultipole for site I.
227    * @param mK PolarizableMultipole for site K.
228    */
229   public void setAndRotate(DoubleVector dx, DoubleVector dy, DoubleVector dz,
230                            PolarizableMultipoleSIMD mI, PolarizableMultipoleSIMD mK) {
231     setQIVector(dx, dy, dz);
232     rotatePolarizableMultipole(mI);
233     rotatePolarizableMultipole(mK);
234   }
235 
236   /**
237    * Rotate the permanent multipole and induced dipole.
238    *
239    * @param m PolarizableMultipole to rotate.
240    */
241   public void rotatePolarizableMultipole(PolarizableMultipoleSIMD m) {
242     rotatePermanentMultipole(m);
243     rotateInducedDipoles(m);
244   }
245 
246   /**
247    * Rotate the permanent multipole.
248    *
249    * @param m PolarizableMultipole to rotate.
250    */
251   public void rotatePermanentMultipole(PolarizableMultipoleSIMD m) {
252     // Rotate the permanent dipole.
253     DoubleVector dx = m.dx;
254     DoubleVector dy = m.dy;
255     DoubleVector dz = m.dz;
256     m.dx = r00Vec.mul(dx).add(r01Vec.mul(dy)).add(r02Vec.mul(dz));
257     m.dy = r10Vec.mul(dx).add(r11Vec.mul(dy)).add(r12Vec.mul(dz));
258     m.dz = r20Vec.mul(dx).add(r21Vec.mul(dy)).add(r22Vec.mul(dz));
259 
260     // Rotate the quadrupole.
261     DoubleVector qxx = m.qxx;
262     DoubleVector qyy = m.qyy;
263     DoubleVector qzz = m.qzz;
264     // The Multipole class stores 2.0 times the off-diagonal components.
265     DoubleVector qxy = m.qxy.mul(0.5);
266     DoubleVector qxz = m.qxz.mul(0.5);
267     DoubleVector qyz = m.qyz.mul(0.5);
268 
269     m.qxx = r00Vec.mul(r00Vec.mul(qxx).add(r01Vec.mul(qxy)).add(r02Vec.mul(qxz)))
270         .add(r01Vec.mul((r00Vec.mul(qxy).add(r01Vec.mul(qyy)).add(r02Vec.mul(qyz)))))
271         .add(r02Vec.mul((r00Vec.mul(qxz).add(r01Vec.mul(qyz)).add(r02Vec.mul(qzz)))));
272 
273     m.qxy = r00Vec.mul(r10Vec.mul(qxx).add(r11Vec.mul(qxy)).add(r12Vec.mul(qxz)))
274         .add(r01Vec.mul((r10Vec.mul(qxy).add(r11Vec.mul(qyy)).add(r12Vec.mul(qyz)))))
275         .add(r02Vec.mul((r10Vec.mul(qxz).add(r11Vec.mul(qyz)).add(r12Vec.mul(qzz)))));
276     m.qxy = m.qxy.mul(2.0);
277 
278     m.qxz = r00Vec.mul(r20Vec.mul(qxx).add(r21Vec.mul(qxy)).add(r22Vec.mul(qxz)))
279         .add(r01Vec.mul((r20Vec.mul(qxy).add(r21Vec.mul(qyy)).add(r22Vec.mul(qyz)))))
280         .add(r02Vec.mul((r20Vec.mul(qxz).add(r21Vec.mul(qyz)).add(r22Vec.mul(qzz)))));
281     m.qxz = m.qxz.mul(2.0);
282 
283     m.qyy = r10Vec.mul(r10Vec.mul(qxx).add(r11Vec.mul(qxy)).add(r12Vec.mul(qxz)))
284         .add(r11Vec.mul((r10Vec.mul(qxy).add(r11Vec.mul(qyy)).add(r12Vec.mul(qyz)))))
285         .add(r12Vec.mul((r10Vec.mul(qxz).add(r11Vec.mul(qyz)).add(r12Vec.mul(qzz)))));
286 
287     m.qyz = r10Vec.mul(r20Vec.mul(qxx).add(r21Vec.mul(qxy)).add(r22Vec.mul(qxz)))
288         .add(r11Vec.mul((r20Vec.mul(qxy).add(r21Vec.mul(qyy)).add(r22Vec.mul(qyz)))))
289         .add(r12Vec.mul((r20Vec.mul(qxz).add(r21Vec.mul(qyz)).add(r22Vec.mul(qzz)))));
290     m.qyz = m.qyz.mul(2.0);
291 
292     m.qzz = r20Vec.mul(r20Vec.mul(qxx).add(r21Vec.mul(qxy)).add(r22Vec.mul(qxz)))
293         .add(r21Vec.mul((r20Vec.mul(qxy).add(r21Vec.mul(qyy)).add(r22Vec.mul(qyz)))))
294         .add(r22Vec.mul((r20Vec.mul(qxz).add(r21Vec.mul(qyz)).add(r22Vec.mul(qzz)))));
295 
296   }
297 
298   /**
299    * Rotate the induced dipoles components.
300    *
301    * @param m PolarizableMultipole to rotate.
302    */
303   public void rotateInducedDipoles(PolarizableMultipoleSIMD m) {
304     DoubleVector dx = m.ux;
305     DoubleVector dy = m.uy;
306     DoubleVector dz = m.uz;
307     m.ux = r00Vec.mul(dx).add(r01Vec.mul(dy)).add(r02Vec.mul(dz));
308     m.uy = r10Vec.mul(dx).add(r11Vec.mul(dy)).add(r12Vec.mul(dz));
309     m.uz = r20Vec.mul(dx).add(r21Vec.mul(dy)).add(r22Vec.mul(dz));
310     dx = m.px;
311     dy = m.py;
312     dz = m.pz;
313     m.px = r00Vec.mul(dx).add(r01Vec.mul(dy)).add(r02Vec.mul(dz));
314     m.py = r10Vec.mul(dx).add(r11Vec.mul(dy)).add(r12Vec.mul(dz));
315     m.pz = r20Vec.mul(dx).add(r21Vec.mul(dy)).add(r22Vec.mul(dz));
316     // Set update the averaged induced and chain rule terms.
317     m.sx = m.ux.add(m.px).mul(0.5);
318     m.sy = m.uy.add(m.py).mul(0.5);
319     m.sz = m.uz.add(m.pz).mul(0.5);
320   }
321 
322   /**
323    * Rotate a vector in the QI frame into the global frame.
324    *
325    * @param v The vector to rotate (in-place).
326    */
327   public void toGlobal(DoubleVector[] v) {
328     DoubleVector vx = v[0];
329     DoubleVector vy = v[1];
330     DoubleVector vz = v[2];
331     // X-component.
332     v[0] = ir00Vec.mul(vx).add(ir01Vec.mul(vy)).add(ir02Vec.mul(vz));
333     // Y-component.
334     v[1] = ir10Vec.mul(vx).add(ir11Vec.mul(vy)).add(ir12Vec.mul(vz));
335     // Z-component.
336     v[2] = ir20Vec.mul(vx).add(ir21Vec.mul(vy)).add(ir22Vec.mul(vz));
337   }
338 }