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.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     * Logger for the Superpose Class.
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     * Parallel Java world communicator.
90     */
91    private final Comm world;
92    /**
93     * Number of processes.
94     */
95    private final int numProc;
96    /**
97     * Rank of this process.
98     */
99    private final int rank;
100   /**
101    * The distances matrix stores a single RMSD value from each process. The array is of size
102    * [numProc][1].
103    */
104   private final double[][] distances;
105   /**
106    * Each distance is wrapped inside a DoubleBuf for MPI communication.
107    */
108   private final DoubleBuf[] buffers;
109   /**
110    * Convenience reference to the RMSD value for this process.
111    */
112   private final double[] myDistance;
113   /**
114    * Convenience reference for the DoubleBuf of this process.
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     // Number of models to be evaluated.
125     baseSize = baseFilter.countNumModels();
126     targetSize = targetFilter.countNumModels();
127 
128     // Initialize the distance matrix row.
129     distRow = new double[targetSize];
130     fill(distRow, -1.0);
131 
132     world = Comm.world();
133     // Number of processes is equal to world size (often called size).
134     numProc = world.size();
135     // Each processor gets its own rank (ID of sorts).
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     // Initialize each distance as -1.0.
145     for (int i = 0; i < numProc; i++) {
146       fill(distances[i], -1.0);
147     }
148 
149     // DoubleBuf is a wrapper used by Comm to transfer data between processors.
150     buffers = new DoubleBuf[numProc];
151     for (int i = 0; i < numProc; i++) {
152       buffers[i] = DoubleBuf.buffer(distances[i]);
153     }
154 
155     // Reference to each processor's individual task (for convenience).
156     myDistance = distances[rank];
157     myBuffer = buffers[rank];
158   }
159 
160   /**
161    * This method calculates the all versus all RMSD of a multiple model pdb/arc file.
162    *
163    * @param usedIndices List of atom indices in use.
164    * @param dRMSD Compute dRMSDs in addition to RMSD.
165    * @param verbose Verbose logging.
166    * @param restart Attempt to restart from an RMSD matrix.
167    * @param write Write out an RMSD file.
168    * @param saveSnapshots Save superposed snapshots.
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     // Prepare to write out superposed snapshots.
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       // Define the filename to use for the RMSD values.
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     // Number of atoms used for the superposition
216     int nUsed = usedIndices.length;
217     int nUsedVars = nUsed * 3;
218 
219     // Load atomic coordinates for the base system.
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     // Load an array for mass weighting.
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     // Allocate space for the superposition
235     MolecularAssembly targetMolecularAssembly = targetFilter.getActiveMolecularSystem();
236     ForceFieldEnergy targetForceFieldEnergy = targetMolecularAssembly.getPotentialEnergy();
237     double[] targetCoords = new double[nVars];
238     double[] targetUsedCoords = new double[nUsedVars];
239 
240     // Read ahead to the base starting conformation.
241     for (int row = 0; row < restartRow; row++) {
242       baseFilter.readNext(false, false);
243     }
244 
245     // Padding of the target array size (inner loop limit) is for parallelization.
246     // Target conformations are parallelized over available nodes.
247     // For example, if numProc = 8 and targetSize = 12, then paddedTargetSize = 16.
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     // Loop over base structures.
256     for (int row = restartRow; row < baseSize; row++) {
257 
258       // Initialize the distance this rank is responsible for to zero.
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       // Loop over target structures.
272       for (int column = restartColumn; column < paddedTargetSize; column++) {
273 
274         // Make sure this is not a padded value of column.
275         if (column < targetSize) {
276           int targetRank = column % numProc;
277           if (targetRank == rank) {
278             if (isSymmetric && row == column) {
279               // Fill the diagonal.
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               // Skip the calculation of the lower triangle.
287               myDistance[0] = -1.0;
288             } else {
289               // Calculate an upper triangle entry.
290 
291               // Load base coordinates.
292               baseForceFieldEnergy.getCoordinates(baseCoords);
293 
294               // Save the current coordinates of the target
295               AssemblyState origStateB = new AssemblyState(targetMolecularAssembly);
296 
297               // Load the target coordinates.
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               // Calculate the translation on only the used subset, but apply it to the entire structure.
306               double[] baseTranslation = calculateTranslation(baseUsedCoords, mass);
307               applyTranslation(baseCoords, baseTranslation);
308               double[] targetTranslation = calculateTranslation(targetUsedCoords, mass);
309               applyTranslation(targetCoords, targetTranslation);
310               // Copy the applied translation to baseUsedCoords and targetUsedCoords.
311               extractCoordinates(usedIndices, baseCoords, baseUsedCoords);
312               extractCoordinates(usedIndices, targetCoords, targetUsedCoords);
313               double translatedRMSD = rmsd(baseUsedCoords, targetUsedCoords, mass);
314 
315               // Calculate the rotation on only the used subset, but apply it to the entire structure.
316               double[][] rotation = calculateRotation(baseUsedCoords, targetUsedCoords, mass);
317               applyRotation(targetCoords, rotation);
318               // Copy the applied rotation to targetUsedCoords.
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               // Store the RMSD result.
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               // Save a PDB snapshot.
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           // Read ahead to the next target structure.
380           targetFilter.readNext(false, false);
381         }
382 
383         // Every numProc iterations, send the results of each rank.
384         if ((column + 1) % numProc == 0) {
385           gatherRMSDs(row, column, runningStatistics);
386         }
387       }
388 
389       // Reset the starting column.
390       restartColumn = 0;
391 
392       // Reset the targetFilter and read the first structure.
393       targetFilter.readNext(true, false);
394 
395       // Read the next base structure.
396       baseFilter.readNext(false, false);
397 
398       // Only Rank 0 writes the distance matrix.
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    * This method calls <code>world.AllGather</code> to collect numProc PAC RMSD values.
422    *
423    * @param row Current row of the PAC RMSD matrix.
424    * @param column Current column of the PAC RMSD matrix.
425    * @param runningStatistics Stats.
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             // Only collect stats for the upper triangle.
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    * Read in the distance matrix.
451    *
452    * @param filename The PAC RMSD matrix file to read from.
453    * @param isSymmetric Is the distance matrix symmetric.
454    * @param expectedRows The expected number of rows.
455    * @param expectedColumns The expected number of columns.
456    * @return Stats for all read in distance matrix values.
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         // Only the diagonal entry (0.0) is on the last row for a symmetric matrix.
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    * Extract used coordinate subset from the entire system.
496    *
497    * @param usedIndices Mapping from the xUsed array to its source in x.
498    * @param x All atomic coordinates.
499    * @param xUsed The used subset of coordinates.
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    * Calculates the dRMSD between to sets of coordinates.
512    *
513    * @param xUsed A double array containing the xyz coordinates for multiple atoms.
514    * @param x2Used A double array containing the xyz coordinates for multiple atoms.
515    * @param nUsed The number of atoms that dRMSD is calculated on.
516    * @return A double containing the dRMSD value.
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    * Apply a rotation matrix to a set of coordinates.
540    *
541    * @param x2 Cartesian coordinates of the second system. Modified in-place.
542    * @param rot A pre-calculated rotation matrix.
543    */
544   public static void applyRotation(double[] x2, double[][] rot) {
545     int n = x2.length / 3;
546 
547     // Rotate second molecule to best fit with first molecule
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    * Apply a translation matrix [dx,dy,dz] to a molecular system.
561    *
562    * @param x Coordinates to move; modified in-place.
563    * @param translation Translation matrix.
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    * Calculate a rotation to minimize RMS distance between two sets of atoms using quaternions,
577    * overlapping x2 on x1.
578    *
579    * @param x1 Cartesian coordinates of the first system.
580    * @param x2 Cartesian coordinates of the second system.
581    * @param mass The mass of each particle in the system.
582    * @return A rotation matrix.
583    */
584   public static double[][] calculateRotation(double[] x1, double[] x2, double[] mass) {
585 
586     // Build the upper triangle of the quadratic form matrix
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     // Diagonalize the quadratic form matrix
631     Array2DRowRealMatrix cMatrix = new Array2DRowRealMatrix(c, false);
632     EigenDecomposition eigenDecomposition = new EigenDecomposition(cMatrix);
633 
634     // Extract the quaternions.
635     double[] q = eigenDecomposition.getEigenvector(0).toArray();
636 
637     // Assemble rotation matrix that superimposes the molecules
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    * Calculate a translation vector [dx,dy,dz] to move the center of mass to the origin.
658    *
659    * @param x Coordinates of the system.
660    * @param mass Mass of each atom.
661    * @return Translation to the origin.
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    * Compute the RMSD for superimposed atom pairs.
687    *
688    * @param x1 Cartesian coordinates of the first system.
689    * @param x2 Cartesian coordinates of the second system.
690    * @param mass The mass of each particle in the system.
691    * @return The RMSD.
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    * Minimize the RMS distance between two sets of atoms using quaternions.
713    *
714    * @param x1 Cartesian coordinates of the first system. Unmodified.
715    * @param x2 Cartesian coordinates of the second system. Modified in-place.
716    * @param mass The mass of each particle in the system.
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    * Move the center of mass for both sets of atoms to the origin.
725    *
726    * @param x1 Cartesian coordinates of the first system.
727    * @param mass1 The mass of each particle in the first system.
728    * @param x2 Cartesian coordinates of the second system.
729    * @param mass2 The mass of each particle in the second system.
730    */
731   public static void translate(double[] x1, double[] mass1, double[] x2, double[] mass2) {
732     // Move the first set of atoms.
733     translate(x1, mass1);
734     // Move the second set of atoms.
735     translate(x2, mass2);
736   }
737 
738   /**
739    * Move the center of mass for a set of atoms to the origin.
740    *
741    * @param x Cartesian coordinates of the system; modified in-place.
742    * @param mass The mass of each particle in the system.
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    * This method completes the following superposition operations and returns an RMSD: 1) translates
751    * the x1 coordinates to the origin. 2) translates the x2 coordinates to the origin. 3) rotates x2
752    * onto x1. 4) computes and return the RMSD.
753    *
754    * @param x1 Coordinates of system 1.
755    * @param x2 Coordinates of system 2.
756    * @param mass Mass of each atom.
757    * @return The mass-weighted RMSD following superposition.
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 }