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 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 }