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