1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.potential.utils;
39
40 import ffx.numerics.math.Double3;
41 import ffx.potential.bonded.Atom;
42 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
43 import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention;
44 import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
45 import org.apache.commons.math3.linear.Array2DRowRealMatrix;
46 import org.apache.commons.math3.linear.EigenDecomposition;
47
48 import java.util.Arrays;
49 import java.util.logging.Logger;
50
51 import static java.lang.String.format;
52 import static org.apache.commons.math3.util.FastMath.PI;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class StructureMetrics {
70
71 private static final Logger logger = Logger.getLogger(StructureMetrics.class.getName());
72
73
74
75
76
77
78
79 public static double radiusOfGyration(Atom[] atoms) {
80 Double3 radius = new Double3(radiusOfGyrationComponents(atoms));
81 return radius.length();
82 }
83
84
85
86
87
88
89
90
91
92 public static double radiusOfGyration(double[] x, double[] y, double[] z, double[] mass) {
93 assert (x.length == y.length);
94 assert (y.length == z.length);
95 Double3 radius = new Double3(radiusOfGyrationComponents(x, y, z, mass, true));
96 return radius.length();
97 }
98
99
100
101
102
103
104
105 public static double radiusOfGyration(double[] xyz, double[] mass) {
106 assert (xyz.length % 3 == 0);
107 Double3 radius = new Double3(radiusOfGyrationComponents(xyz, mass, true));
108 return radius.length();
109 }
110
111
112
113
114
115
116
117 public static double[] radiusOfGyrationComponents(Atom[] atoms) {
118 int nAtoms = atoms.length;
119 double[] x = new double[nAtoms];
120 double[] y = new double[nAtoms];
121 double[] z = new double[nAtoms];
122 double[] mass = new double[nAtoms];
123
124 int index = 0;
125 for (int i = 0; i < nAtoms; i++) {
126 mass[i] = atoms[i].getMass();
127 x[index] = atoms[i].getX();
128 y[index] = atoms[i].getY();
129 z[index] = atoms[i].getZ();
130 index++;
131 }
132
133 return radiusOfGyrationComponents(x, y, z, mass, false);
134 }
135
136
137
138
139
140
141
142 public static double[] radiusOfGyrationComponents(double[] xyz, double[] mass, boolean pmp) {
143 assert (xyz.length % 3 == 0);
144 int nAtoms = xyz.length / 3;
145
146 double[] x = new double[nAtoms];
147 double[] y = new double[nAtoms];
148 double[] z = new double[nAtoms];
149
150 for (int i = 0; i < nAtoms; i++) {
151 int index = i * 3;
152 x[i] = xyz[index++];
153 y[i] = xyz[index++];
154 z[i] = xyz[index];
155 }
156
157 return radiusOfGyrationComponents(x, y, z, mass, pmp);
158 }
159
160
161
162
163
164
165
166
167
168
169 public static double[] radiusOfGyrationComponents(double[] x, double[] y, double[] z,
170 double[] mass, boolean pmp) {
171 assert (x.length == y.length);
172 assert (y.length == z.length);
173 assert (x.length <= mass.length);
174 double massSum = Arrays.stream(mass).sum();
175
176
177 int nAtoms = x.length;
178 double xc = 0.0;
179 double yc = 0.0;
180 double zc = 0.0;
181 for (int i = 0; i < nAtoms; i++) {
182 xc += x[i] * mass[i];
183 yc += y[i] * mass[i];
184 zc += z[i] * mass[i];
185 }
186 Double3 centroid = new Double3(xc, yc, zc);
187 centroid.scaleI(1.0 / massSum);
188
189
190 Double3 radius = new Double3();
191 if (pmp) {
192
193 double xterm;
194 double yterm;
195 double zterm;
196 double xx = 0.0;
197 double xy = 0.0;
198 double xz = 0.0;
199 double yy = 0.0;
200 double yz = 0.0;
201 double zz = 0.0;
202 for (int i = 0; i < nAtoms; i++) {
203 xterm = x[i] - xc;
204 yterm = y[i] - yc;
205 zterm = z[i] - zc;
206 xx += xterm * xterm;
207 xy += xterm * yterm;
208 xz += xterm * zterm;
209 yy += yterm * yterm;
210 yz += yterm * zterm;
211 zz += zterm * zterm;
212 }
213 double[][] tensor = new double[3][3];
214 tensor[0][0] = xx;
215 tensor[0][1] = xy;
216 tensor[0][2] = xz;
217 tensor[1][0] = xy;
218 tensor[1][1] = yy;
219 tensor[1][2] = yz;
220 tensor[2][0] = xz;
221 tensor[2][1] = yz;
222 tensor[2][2] = zz;
223
224 Array2DRowRealMatrix cMatrix = new Array2DRowRealMatrix(tensor, false);
225 EigenDecomposition eigenDecomposition = new EigenDecomposition(cMatrix);
226
227 radius.set(eigenDecomposition.getRealEigenvalues()).scaleI(1.0 / nAtoms).sqrtI();
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 } else {
252 for (int i = 0; i < nAtoms; i++) {
253 Double3 xyz = new Double3(x[i], y[i], z[i]);
254 xyz.subI(centroid);
255 radius.addI(xyz.squareI());
256 }
257 radius.scaleI(1.0 / nAtoms).sqrtI();
258 }
259
260 return radius.get();
261 }
262
263
264
265
266
267
268
269
270
271
272 public static double[][] momentsOfInertia(Atom[] atoms, boolean moved, boolean print,
273 boolean pma) {
274 double[] mass = new double[atoms.length];
275 int nAtoms = atoms.length;
276 double[] x = new double[nAtoms];
277 double[] y = new double[nAtoms];
278 double[] z = new double[nAtoms];
279
280 int index = 0;
281 for (Atom atom : atoms) {
282 mass[index] = atom.getMass();
283 x[index] = atom.getX();
284 y[index] = atom.getY();
285 z[index] = atom.getZ();
286 index++;
287 }
288
289 return momentsOfInertia(x, y, z, mass, moved, print, pma);
290 }
291
292
293
294
295
296
297
298
299
300
301
302 public static double[][] momentsOfInertia(double[] xyz, double[] mass, boolean moved,
303 boolean print, boolean pma) {
304 assert (xyz.length % 3 == 0);
305 int nAtoms = xyz.length / 3;
306
307 double[] x = new double[nAtoms];
308 double[] y = new double[nAtoms];
309 double[] z = new double[nAtoms];
310
311 for (int i = 0; i < nAtoms; i++) {
312 int index = i * 3;
313 x[i] = xyz[index++];
314 y[i] = xyz[index++];
315 z[i] = xyz[index];
316 }
317
318 return momentsOfInertia(x, y, z, mass, moved, print, pma);
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333 public static double[][] momentsOfInertia(double[] x, double[] y, double[] z, double[] mass,
334 boolean moved, boolean print, boolean pma) {
335 assert (x.length == y.length);
336 assert (y.length == z.length);
337
338
339 double total = 0.0;
340 double xcm = 0.0;
341 double ycm = 0.0;
342 double zcm = 0.0;
343 int nAtoms = x.length;
344 for (int i = 0; i < nAtoms; i++) {
345 double massValue = mass[i];
346 total += massValue;
347 xcm += x[i] * massValue;
348 ycm += y[i] * massValue;
349 zcm += z[i] * massValue;
350 }
351 xcm /= total;
352 ycm /= total;
353 zcm /= total;
354
355
356 double xx = 0.0;
357 double xy = 0.0;
358 double xz = 0.0;
359 double yy = 0.0;
360 double yz = 0.0;
361 double zz = 0.0;
362
363 double xterm;
364 double yterm;
365 double zterm;
366
367 for (int i = 0; i < nAtoms; i++) {
368 double massValue = mass[i];
369 xterm = x[i] - xcm;
370 yterm = y[i] - ycm;
371 zterm = z[i] - zcm;
372 xx += xterm * xterm * massValue;
373 xy += xterm * yterm * massValue;
374 xz += xterm * zterm * massValue;
375 yy += yterm * yterm * massValue;
376 yz += yterm * zterm * massValue;
377 zz += zterm * zterm * massValue;
378 }
379 double[][] tensor = new double[3][3];
380 tensor[0][0] = yy + zz;
381 tensor[0][1] = -xy;
382 tensor[0][2] = -xz;
383 tensor[1][0] = -xy;
384 tensor[1][1] = xx + zz;
385 tensor[1][2] = -yz;
386 tensor[2][0] = -xz;
387 tensor[2][1] = -yz;
388 tensor[2][2] = xx + yy;
389
390 double[] moment;
391 double[][] vec;
392 if (pma) {
393
394 Array2DRowRealMatrix cMatrix = new Array2DRowRealMatrix(tensor, false);
395 EigenDecomposition eigenDecomposition = new EigenDecomposition(cMatrix);
396
397 moment = eigenDecomposition.getRealEigenvalues();
398 vec = eigenDecomposition.getV().getData();
399
400
401 double dot = 0.0;
402 for (int i = 0; i < 2; i++) {
403 for (int j = 0; j < nAtoms; j++) {
404 xterm = vec[i][0] * (x[j] - xcm);
405 yterm = vec[i][1] * (y[j] - ycm);
406 zterm = vec[i][2] * (z[j] - zcm);
407 dot = xterm + yterm + zterm;
408 if (dot < 0.0) {
409 for (int k = 0; k < 3; k++) {
410 vec[i][k] = -vec[i][k];
411 }
412 }
413 if (dot != 0.0) {
414 break;
415 }
416 }
417 if (dot != 0.0) {
418 break;
419 }
420 }
421
422
423 xterm = vec[0][0] * (vec[1][1] * vec[2][2] - vec[2][1] * vec[1][2]);
424 yterm = vec[0][1] * (vec[2][0] * vec[1][2] - vec[1][0] * vec[2][2]);
425 zterm = vec[0][2] * (vec[1][0] * vec[2][1] - vec[2][0] * vec[1][1]);
426 dot = xterm + yterm + zterm;
427 if (dot < 0.0) {
428 for (int i = 0; i < 3; i++) {
429 vec[2][i] = -vec[2][i];
430 }
431 }
432
433
434 if (moved) {
435 double[][] a = new double[3][3];
436 for (int i = 0; i < 3; i++) {
437 for (int j = 0; j < 3; j++) {
438 a[j][i] = vec[j][i];
439 }
440 }
441
442 for (int i = 0; i < nAtoms; i++) {
443 xterm = x[i] - xcm;
444 yterm = y[i] - ycm;
445 zterm = z[i] - zcm;
446 x[i] = a[0][0] * xterm + a[1][0] * yterm + a[2][0] * zterm;
447 y[i] = a[0][1] * xterm + a[1][1] * yterm + a[2][1] * zterm;
448 z[i] = a[0][2] * xterm + a[1][2] * yterm + a[2][2] * zterm;
449 }
450 }
451 } else {
452 vec = new double[][]{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
453 moment = new double[]{tensor[0][0], tensor[1][1], tensor[2][2]};
454 }
455
456
457 if (print) {
458 logger.info(format("\n Center of Mass Coordinates: %8.4f %8.4f %8.4f", xcm, ycm, zcm));
459
460 double[] angles = new Rotation(vec, 1.0E-7).getAngles(
461 RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR);
462 double radian = 180 / PI;
463
464 for (int i = 0; i < 3; i++) {
465 angles[i] *= radian;
466 }
467 logger.info(format(" Euler Angles (Phi/Theta/Psi): %8.3f %8.3f %8.3f", angles[0], angles[1], angles[2]));
468 logger.info(
469 " Moments of Inertia and Principle Axes:\n Moments (amu Ang^2): \t X-, Y-, and Z-Components of Axes:");
470 for (int i = 0; i < 3; i++) {
471 logger.info(format(" %16.3f %12.6f %12.6f %12.6f", moment[i], vec[i][0], vec[i][1], vec[i][2]));
472 }
473 }
474
475 double[][] momentsAndVectors = new double[3][4];
476 for (int i = 0; i < 3; i++) {
477 for (int j = 0; j < 4; j++) {
478 if (j == 0) {
479 momentsAndVectors[i][j] = moment[i];
480 } else {
481 momentsAndVectors[i][j] = vec[i][j - 1];
482 }
483 }
484 }
485
486 return momentsAndVectors;
487 }
488 }