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 edu.rit.mp.DoubleBuf;
41 import edu.rit.pj.Comm;
42 import ffx.crystal.SymOp;
43 import ffx.numerics.math.Double3;
44 import ffx.numerics.math.RunningStatistics;
45 import ffx.potential.AssemblyState;
46 import ffx.potential.ForceFieldEnergy;
47 import ffx.potential.MolecularAssembly;
48 import ffx.potential.bonded.Atom;
49 import ffx.potential.parsers.DistanceMatrixFilter;
50 import ffx.potential.parsers.SystemFilter;
51 import ffx.potential.parsers.XYZFilter;
52 import org.apache.commons.math3.linear.Array2DRowRealMatrix;
53 import org.apache.commons.math3.linear.EigenDecomposition;
54
55 import java.io.File;
56 import java.util.ArrayList;
57 import java.util.logging.Logger;
58
59 import static ffx.crystal.SymOp.asMatrixString;
60 import static ffx.potential.parsers.DistanceMatrixFilter.writeDistanceMatrixRow;
61 import static ffx.utilities.StringUtils.writeAtomRanges;
62 import static java.lang.String.format;
63 import static java.lang.System.arraycopy;
64 import static java.util.Arrays.fill;
65 import static org.apache.commons.io.FilenameUtils.concat;
66 import static org.apache.commons.io.FilenameUtils.getBaseName;
67 import static org.apache.commons.io.FilenameUtils.getFullPath;
68 import static org.apache.commons.math3.util.FastMath.sqrt;
69
70 public class Superpose {
71
72
73
74
75 private static final Logger logger = Logger.getLogger(Superpose.class.getName());
76
77 private final SystemFilter baseFilter;
78 private final SystemFilter targetFilter;
79 private final boolean isSymmetric;
80
81 private final int baseSize;
82 private final int targetSize;
83
84 private int restartRow;
85 private int restartColumn;
86 private final double[] distRow;
87
88
89
90
91 private final Comm world;
92
93
94
95 private final int numProc;
96
97
98
99 private final int rank;
100
101
102
103
104 private final double[][] distances;
105
106
107
108 private final DoubleBuf[] buffers;
109
110
111
112 private final double[] myDistance;
113
114
115
116 private final DoubleBuf myBuffer;
117
118
119 public Superpose(SystemFilter baseFilter, SystemFilter targetFilter, boolean isSymmetric) {
120 this.baseFilter = baseFilter;
121 this.targetFilter = targetFilter;
122 this.isSymmetric = isSymmetric;
123
124
125 baseSize = baseFilter.countNumModels();
126 targetSize = targetFilter.countNumModels();
127
128
129 distRow = new double[targetSize];
130 fill(distRow, -1.0);
131
132 world = Comm.world();
133
134 numProc = world.size();
135
136 rank = world.rank();
137 if (numProc > 1) {
138 logger.info(format(" Number of MPI Processes: %d", numProc));
139 logger.info(format(" Rank of this MPI Process: %d", rank));
140 }
141
142 distances = new double[numProc][1];
143
144
145 for (int i = 0; i < numProc; i++) {
146 fill(distances[i], -1.0);
147 }
148
149
150 buffers = new DoubleBuf[numProc];
151 for (int i = 0; i < numProc; i++) {
152 buffers[i] = DoubleBuf.buffer(distances[i]);
153 }
154
155
156 myDistance = distances[rank];
157 myBuffer = buffers[rank];
158 }
159
160
161
162
163
164
165
166
167
168
169
170 public void calculateRMSDs(int[] usedIndices, boolean dRMSD, boolean verbose, boolean restart,
171 boolean write, boolean saveSnapshots, boolean printSym) {
172
173 String filename = baseFilter.getFile().getAbsolutePath();
174 String targetFilename = targetFilter.getFile().getAbsolutePath();
175
176
177 File baseOutputFile = null;
178 File targetOutputFile = null;
179 SystemFilter baseOutputFilter = null;
180 SystemFilter targetOutputFilter = null;
181 if (saveSnapshots) {
182 String baseOutputName = concat(getFullPath(filename),
183 getBaseName(filename) + "_superposed.arc");
184 String targetOutputName = concat(getFullPath(filename),
185 getBaseName(targetFilename) + "_superposed.arc");
186 baseOutputFile = SystemFilter.version(new File(baseOutputName));
187 targetOutputFile = SystemFilter.version(new File(targetOutputName));
188 MolecularAssembly baseAssembly = baseFilter.getActiveMolecularSystem();
189 MolecularAssembly targetAssembly = targetFilter.getActiveMolecularSystem();
190 baseOutputFilter = new XYZFilter(baseOutputFile, baseAssembly,
191 baseAssembly.getForceField(),
192 baseAssembly.getProperties());
193 targetOutputFilter = new XYZFilter(targetOutputFile, targetAssembly,
194 targetAssembly.getForceField(),
195 targetAssembly.getProperties());
196 }
197
198 String matrixFilename = concat(getFullPath(filename), getBaseName(filename) + ".dst");
199 RunningStatistics runningStatistics;
200 if (restart) {
201
202 runningStatistics = readMatrix(matrixFilename, isSymmetric, baseSize, targetSize);
203 if (runningStatistics == null) {
204 runningStatistics = new RunningStatistics();
205 }
206 } else {
207 runningStatistics = new RunningStatistics();
208 File file = new File(matrixFilename);
209 if (file.exists() && file.delete()) {
210 logger.info(format(" RMSD file (%s) was deleted.", matrixFilename));
211 logger.info(" To restart from a previous run, use the '-r' flag.");
212 }
213 }
214
215
216 int nUsed = usedIndices.length;
217 int nUsedVars = nUsed * 3;
218
219
220 MolecularAssembly baseMolecularAssembly = baseFilter.getActiveMolecularSystem();
221 ForceFieldEnergy baseForceFieldEnergy = baseMolecularAssembly.getPotentialEnergy();
222 int nVars = baseForceFieldEnergy.getNumberOfVariables();
223 double[] baseCoords = new double[nVars];
224 baseForceFieldEnergy.getCoordinates(baseCoords);
225 double[] baseUsedCoords = new double[nUsedVars];
226
227
228 Atom[] atoms = baseMolecularAssembly.getAtomArray();
229 double[] mass = new double[nUsed];
230 for (int i = 0; i < nUsed; i++) {
231 mass[i] = atoms[usedIndices[i]].getMass();
232 }
233
234
235 MolecularAssembly targetMolecularAssembly = targetFilter.getActiveMolecularSystem();
236 ForceFieldEnergy targetForceFieldEnergy = targetMolecularAssembly.getPotentialEnergy();
237 double[] targetCoords = new double[nVars];
238 double[] targetUsedCoords = new double[nUsedVars];
239
240
241 for (int row = 0; row < restartRow; row++) {
242 baseFilter.readNext(false, false);
243 }
244
245
246
247
248 int paddedTargetSize = targetSize;
249 int extra = targetSize % numProc;
250 if (extra != 0) {
251 paddedTargetSize = targetSize - extra + numProc;
252 logger.fine(format(" Target size %d vs. Padded size %d", targetSize, paddedTargetSize));
253 }
254
255
256 for (int row = restartRow; row < baseSize; row++) {
257
258
259 myDistance[0] = -1.0;
260
261 if (row == restartRow) {
262 if (dRMSD) {
263 logger.info(
264 "\n Coordinate RMSD\n Snapshots Original After Translation After Rotation dRMSD");
265 } else if (verbose) {
266 logger.info(
267 "\n Coordinate RMSD\n Snapshots Original After Translation After Rotation");
268 }
269 }
270
271
272 for (int column = restartColumn; column < paddedTargetSize; column++) {
273
274
275 if (column < targetSize) {
276 int targetRank = column % numProc;
277 if (targetRank == rank) {
278 if (isSymmetric && row == column) {
279
280 myDistance[0] = 0.0;
281 if (verbose) {
282 logger.info(format(" %6d %6d %s %8.5f",
283 row + 1, column + 1, "Diagonal", myDistance[0]));
284 }
285 } else if (isSymmetric && row > column) {
286
287 myDistance[0] = -1.0;
288 } else {
289
290
291
292 baseForceFieldEnergy.getCoordinates(baseCoords);
293
294
295 AssemblyState origStateB = new AssemblyState(targetMolecularAssembly);
296
297
298 targetForceFieldEnergy.getCoordinates(targetCoords);
299
300 extractCoordinates(usedIndices, baseCoords, baseUsedCoords);
301 extractCoordinates(usedIndices, targetCoords, targetUsedCoords);
302
303 double origRMSD = rmsd(baseUsedCoords, targetUsedCoords, mass);
304
305
306 double[] baseTranslation = calculateTranslation(baseUsedCoords, mass);
307 applyTranslation(baseCoords, baseTranslation);
308 double[] targetTranslation = calculateTranslation(targetUsedCoords, mass);
309 applyTranslation(targetCoords, targetTranslation);
310
311 extractCoordinates(usedIndices, baseCoords, baseUsedCoords);
312 extractCoordinates(usedIndices, targetCoords, targetUsedCoords);
313 double translatedRMSD = rmsd(baseUsedCoords, targetUsedCoords, mass);
314
315
316 double[][] rotation = calculateRotation(baseUsedCoords, targetUsedCoords, mass);
317 applyRotation(targetCoords, rotation);
318
319 extractCoordinates(usedIndices, targetCoords, targetUsedCoords);
320 double rotatedRMSD = rmsd(baseUsedCoords, targetUsedCoords, mass);
321
322 if (dRMSD) {
323 double disRMSD = calcDRMSD(baseUsedCoords, targetUsedCoords, nUsed * 3);
324 logger.info(format(" %6d %6d %8.5f %8.5f %8.5f %8.5f",
325 row + 1, column + 1, origRMSD, translatedRMSD, rotatedRMSD, disRMSD));
326 } else if (verbose) {
327 logger.info(format(" %6d %6d %8.5f %8.5f %8.5f",
328 row + 1, column + 1, origRMSD, translatedRMSD, rotatedRMSD));
329 }
330
331
332 myDistance[0] = rotatedRMSD;
333
334 if(printSym){
335 StringBuilder sbSO = new StringBuilder(
336 format("\n Sym Op to move %s onto %s:\nsymop ", targetFilename, filename));
337 StringBuilder sbInv = new StringBuilder(
338 format("\n Inverted Sym Op to move %s onto %s:\nsymop ", filename, targetFilename));
339 SymOp bestBaseSymOp = new SymOp(SymOp.ZERO_ROTATION, SymOp.Tr_0_0_0).append(new SymOp(SymOp.ZERO_ROTATION, baseTranslation).append(SymOp.invertSymOp(new SymOp(rotation, targetTranslation))));
340 double[] inverseBaseTranslation = new double[]{-baseTranslation[0], -baseTranslation[1], -baseTranslation[2]};
341 SymOp bestTargetSymOp = new SymOp(SymOp.ZERO_ROTATION, SymOp.Tr_0_0_0).append(new SymOp(SymOp.ZERO_ROTATION, targetTranslation).append(new SymOp(rotation, inverseBaseTranslation)));
342 ArrayList<Integer> mol1List = new ArrayList<>();
343 ArrayList<Integer> mol2List = new ArrayList<>();
344 Atom[] atomArr1 = baseMolecularAssembly.getAtomArray();
345 Atom[] atomArr2 = targetMolecularAssembly.getAtomArray();
346 int nAtoms = atomArr1.length;
347 for(int i = 0; i < nAtoms; i++){
348 if(atomArr1[i].isActive()){
349 mol1List.add(i);
350 }
351 }
352 nAtoms = atomArr2.length;
353 for(int i = 0; i < nAtoms; i++){
354 if(atomArr2[i].isActive()){
355 mol2List.add(i);
356 }
357 }
358 int[] mol1arr = mol1List.stream().mapToInt(Integer::intValue).toArray();
359 int[] mol2arr = mol2List.stream().mapToInt(Integer::intValue).toArray();
360 sbSO.append(format(" %s %s", writeAtomRanges(mol2arr), writeAtomRanges(mol1arr)))
361 .append(asMatrixString(bestBaseSymOp));
362 sbInv.append(format(" %s %s", writeAtomRanges(mol2arr), writeAtomRanges(mol1arr))).append(asMatrixString(bestTargetSymOp));
363 logger.info(format(" %s\n %s", sbSO, sbInv));
364 }
365
366
367 if (saveSnapshots && numProc == 1) {
368 MolecularAssembly molecularAssembly = targetOutputFilter.getActiveMolecularSystem();
369 molecularAssembly.getPotentialEnergy().setCoordinates(targetCoords);
370 targetOutputFilter.writeFile(targetOutputFile, true);
371 molecularAssembly = baseOutputFilter.getActiveMolecularSystem();
372 molecularAssembly.getPotentialEnergy().setCoordinates(baseCoords);
373 baseOutputFilter.writeFile(baseOutputFile, true);
374 origStateB.revertState();
375 }
376 }
377 }
378
379
380 targetFilter.readNext(false, false);
381 }
382
383
384 if ((column + 1) % numProc == 0) {
385 gatherRMSDs(row, column, runningStatistics);
386 }
387 }
388
389
390 restartColumn = 0;
391
392
393 targetFilter.readNext(true, false);
394
395
396 baseFilter.readNext(false, false);
397
398
399 if (rank == 0 && write) {
400 int firstColumn = 0;
401 if (isSymmetric) {
402 firstColumn = row;
403 }
404 writeDistanceMatrixRow(matrixFilename, distRow, firstColumn);
405 }
406 }
407
408 baseFilter.closeReader();
409 targetFilter.closeReader();
410
411 logger.info(format(" RMSD Minimum: %8.6f", runningStatistics.getMin()));
412 logger.info(format(" RMSD Maximum: %8.6f", runningStatistics.getMax()));
413 logger.info(format(" RMSD Mean: %8.6f", runningStatistics.getMean()));
414 double variance = runningStatistics.getVariance();
415 if (!Double.isNaN(variance)) {
416 logger.info(format(" RMSD Variance: %8.6f", variance));
417 }
418 }
419
420
421
422
423
424
425
426
427 private void gatherRMSDs(int row, int column, RunningStatistics runningStatistics) {
428 try {
429 logger.finer(" Receiving results.");
430 world.allGather(myBuffer, buffers);
431 for (int i = 0; i < numProc; i++) {
432 int c = (column + 1) - numProc + i;
433 if (c < targetSize) {
434 distRow[c] = distances[i][0];
435 if (!isSymmetric) {
436 runningStatistics.addValue(distRow[c]);
437 } else if (c > row) {
438
439 runningStatistics.addValue(distRow[c]);
440 }
441 logger.finer(format(" %d %d %16.8f", row, c, distances[i][0]));
442 }
443 }
444 } catch (Exception e) {
445 logger.severe(" Exception collecting distance values." + e);
446 }
447 }
448
449
450
451
452
453
454
455
456
457
458 private RunningStatistics readMatrix(String filename, boolean isSymmetric, int expectedRows,
459 int expectedColumns) {
460 restartRow = 0;
461 restartColumn = 0;
462
463 DistanceMatrixFilter distanceMatrixFilter = new DistanceMatrixFilter();
464 RunningStatistics runningStatistics = distanceMatrixFilter.readDistanceMatrix(
465 filename, expectedRows, expectedColumns);
466
467 if (runningStatistics != null && runningStatistics.getCount() > 0) {
468 restartRow = distanceMatrixFilter.getRestartRow();
469 restartColumn = distanceMatrixFilter.getRestartColumn();
470
471 if (isSymmetric) {
472
473 if (restartRow == expectedRows && restartColumn == 1) {
474 logger.info(format(" Complete symmetric distance matrix found (%d x %d).", restartRow,
475 restartRow));
476 } else {
477 restartColumn = 0;
478 logger.info(format(
479 " Incomplete symmetric distance matrix found.\n Restarting at row %d, column %d.",
480 restartRow + 1, restartColumn + 1));
481 }
482 } else if (restartRow == expectedRows && restartColumn == expectedColumns) {
483 logger.info(format(" Complete distance matrix found (%d x %d).", restartRow, restartColumn));
484 } else {
485 restartColumn = 0;
486 logger.info(format(" Incomplete distance matrix found.\n Restarting at row %d, column %d.",
487 restartRow + 1, restartColumn + 1));
488 }
489 }
490
491 return runningStatistics;
492 }
493
494
495
496
497
498
499
500
501 public static void extractCoordinates(int[] usedIndices, double[] x, double[] xUsed) {
502 int nUsed = usedIndices.length;
503 for (int u = 0; u < nUsed; u++) {
504 int u3 = 3 * u;
505 int i3 = 3 * usedIndices[u];
506 arraycopy(x, i3, xUsed, u3, 3);
507 }
508 }
509
510
511
512
513
514
515
516
517
518 public static double calcDRMSD(double[] xUsed, double[] x2Used, int nUsed) {
519 double disRMSD = 0.0;
520 int counter = 0;
521 for (int i = 0; i < nUsed; i = i + 3) {
522 Double3 xi = new Double3(xUsed[i], xUsed[i + 1], xUsed[i + 2]);
523 Double3 x2i = new Double3(x2Used[i], x2Used[i + 1], x2Used[i + 2]);
524 for (int j = i + 3; j < nUsed; j = j + 3) {
525 Double3 xj = new Double3(xUsed[j], xUsed[j + 1], xUsed[j + 2]);
526 Double3 x2j = new Double3(x2Used[j], x2Used[j + 1], x2Used[j + 2]);
527 double dis1 = xi.sub(xj).length();
528 double dis2 = x2i.sub(x2j).length();
529 double diff = dis1 - dis2;
530 disRMSD += diff * diff;
531 counter++;
532 }
533 }
534 disRMSD = disRMSD / counter;
535 return sqrt(disRMSD);
536 }
537
538
539
540
541
542
543
544 public static void applyRotation(double[] x2, double[][] rot) {
545 int n = x2.length / 3;
546
547
548 for (int i = 0; i < n; i++) {
549 int k = i * 3;
550 double xrot = x2[k] * rot[0][0] + x2[k + 1] * rot[0][1] + x2[k + 2] * rot[0][2];
551 double yrot = x2[k] * rot[1][0] + x2[k + 1] * rot[1][1] + x2[k + 2] * rot[1][2];
552 double zrot = x2[k] * rot[2][0] + x2[k + 1] * rot[2][1] + x2[k + 2] * rot[2][2];
553 x2[k] = xrot;
554 x2[k + 1] = yrot;
555 x2[k + 2] = zrot;
556 }
557 }
558
559
560
561
562
563
564
565 public static void applyTranslation(double[] x, final double[] translation) {
566 int n = x.length / 3;
567 for (int i = 0; i < n; i++) {
568 int k = i * 3;
569 for (int j = 0; j < 3; j++) {
570 x[k + j] += translation[j];
571 }
572 }
573 }
574
575
576
577
578
579
580
581
582
583
584 public static double[][] calculateRotation(double[] x1, double[] x2, double[] mass) {
585
586
587 double xxyx = 0.0;
588 double xxyy = 0.0;
589 double xxyz = 0.0;
590 double xyyx = 0.0;
591 double xyyy = 0.0;
592 double xyyz = 0.0;
593 double xzyx = 0.0;
594 double xzyy = 0.0;
595 double xzyz = 0.0;
596
597 int n = x1.length / 3;
598 for (int i = 0; i < n; i++) {
599 int k = i * 3;
600 double weigh = mass[i];
601 xxyx = xxyx + weigh * x1[k] * x2[k];
602 xxyy = xxyy + weigh * x1[k + 1] * x2[k];
603 xxyz = xxyz + weigh * x1[k + 2] * x2[k];
604 xyyx = xyyx + weigh * x1[k] * x2[k + 1];
605 xyyy = xyyy + weigh * x1[k + 1] * x2[k + 1];
606 xyyz = xyyz + weigh * x1[k + 2] * x2[k + 1];
607 xzyx = xzyx + weigh * x1[k] * x2[k + 2];
608 xzyy = xzyy + weigh * x1[k + 1] * x2[k + 2];
609 xzyz = xzyz + weigh * x1[k + 2] * x2[k + 2];
610 }
611
612 double[][] c = new double[4][4];
613 c[0][0] = xxyx + xyyy + xzyz;
614 c[0][1] = xzyy - xyyz;
615 c[1][0] = c[0][1];
616 c[1][1] = xxyx - xyyy - xzyz;
617 c[0][2] = xxyz - xzyx;
618 c[2][0] = c[0][2];
619 c[1][2] = xxyy + xyyx;
620 c[2][1] = c[1][2];
621 c[2][2] = xyyy - xzyz - xxyx;
622 c[0][3] = xyyx - xxyy;
623 c[3][0] = c[0][3];
624 c[1][3] = xzyx + xxyz;
625 c[3][1] = c[1][3];
626 c[2][3] = xyyz + xzyy;
627 c[3][2] = c[2][3];
628 c[3][3] = xzyz - xxyx - xyyy;
629
630
631 Array2DRowRealMatrix cMatrix = new Array2DRowRealMatrix(c, false);
632 EigenDecomposition eigenDecomposition = new EigenDecomposition(cMatrix);
633
634
635 double[] q = eigenDecomposition.getEigenvector(0).toArray();
636
637
638 double q02 = q[0] * q[0];
639 double q12 = q[1] * q[1];
640 double q22 = q[2] * q[2];
641 double q32 = q[3] * q[3];
642 double[][] rot = new double[3][3];
643 rot[0][0] = q02 + q12 - q22 - q32;
644 rot[1][0] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
645 rot[2][0] = 2.0 * (q[1] * q[3] + q[0] * q[2]);
646 rot[0][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
647 rot[1][1] = q02 - q12 + q22 - q32;
648 rot[2][1] = 2.0 * (q[2] * q[3] - q[0] * q[1]);
649 rot[0][2] = 2.0 * (q[1] * q[3] - q[0] * q[2]);
650 rot[1][2] = 2.0 * (q[2] * q[3] + q[0] * q[1]);
651 rot[2][2] = q02 - q12 - q22 + q32;
652
653 return rot;
654 }
655
656
657
658
659
660
661
662
663 public static double[] calculateTranslation(double[] x, final double[] mass) {
664 double xmid = 0.0;
665 double ymid = 0.0;
666 double zmid = 0.0;
667 double norm = 0.0;
668 int n = x.length / 3;
669
670 for (int i = 0; i < n; i++) {
671 int k = 3 * i;
672 double weigh = mass[i];
673 xmid += x[k] * weigh;
674 ymid += x[k + 1] * weigh;
675 zmid += x[k + 2] * weigh;
676 norm += weigh;
677 }
678 xmid /= norm;
679 ymid /= norm;
680 zmid /= norm;
681
682 return new double[] {-xmid, -ymid, -zmid};
683 }
684
685
686
687
688
689
690
691
692
693 public static double rmsd(double[] x1, double[] x2, double[] mass) {
694 double rmsfit = 0.0;
695 double norm = 0.0;
696 int n = x1.length / 3;
697 for (int i = 0; i < n; i++) {
698 int k = 3 * i;
699 double weigh = mass[i];
700 double xr = x1[k] - x2[k];
701 double yr = x1[k + 1] - x2[k + 1];
702 double zr = x1[k + 2] - x2[k + 2];
703 double dist2 = xr * xr + yr * yr + zr * zr;
704 norm = norm + weigh;
705 double rmsterm = dist2 * weigh;
706 rmsfit = rmsfit + rmsterm;
707 }
708 return sqrt(rmsfit / norm);
709 }
710
711
712
713
714
715
716
717
718 public static void rotate(double[] x1, double[] x2, double[] mass) {
719 double[][] rotation = calculateRotation(x1, x2, mass);
720 applyRotation(x2, rotation);
721 }
722
723
724
725
726
727
728
729
730
731 public static void translate(double[] x1, double[] mass1, double[] x2, double[] mass2) {
732
733 translate(x1, mass1);
734
735 translate(x2, mass2);
736 }
737
738
739
740
741
742
743
744 public static void translate(double[] x, final double[] mass) {
745 double[] translation = calculateTranslation(x, mass);
746 applyTranslation(x, translation);
747 }
748
749
750
751
752
753
754
755
756
757
758
759 public static double superpose(double[] x1, double[] x2, double[] mass) {
760 translate(x1, mass);
761 translate(x2, mass);
762 rotate(x1, x2, mass);
763 return rmsd(x1, x2, mass);
764 }
765 }