shapeMatch.cpp
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------------
2 // d-SEAMS - Deferred Structural Elucidation Analysis for Molecular Simulations
3 //
4 // Copyright (c) 2018--present d-SEAMS core team
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the MIT License as published by
8 // the Open Source Initiative.
9 //
10 // A copy of the MIT License is included in the LICENSE file of this repository.
11 // You should have received a copy of the MIT License along with this program.
12 // If not, see <https://opensource.org/licenses/MIT>.
13 //-----------------------------------------------------------------------------------
14 
15 #include <shapeMatch.hpp>
16 
23  std::vector<std::vector<int>> nList, const Eigen::MatrixXd &refPoints,
24  std::vector<int> *basal1, std::vector<int> *basal2,
25  std::vector<double> *rmsdPerAtom, bool isPerfect) {
26  //
27  int ringSize = (*basal1).size(); // Number of nodes in each basal ring
28  std::vector<int> matchedBasal1,
29  matchedBasal2; // Re-ordered basal rings 1 and 2
30  int dim = 3; // Number of dimensions
31  // Matrices for the point sets of the target basal rings
32  Eigen::MatrixXd basal1Set(ringSize,
33  dim); // Point set for the first basal ring
34  Eigen::MatrixXd basal2Set(ringSize,
35  dim); // Point set for the second basal ring
36  int startingIndex; // Index in the basal rings from which the reference point
37  // set is matched
38  double cutoffAngle = 15; // Tolerance for the angular distance between the
39  // quaternions of the basal rings
40  double angDist; // Calculated angular distance between the two basal rings
41  // Variables for the absolute orientation
42  std::vector<double> quat1, quat2; // quaternion rotation
43  double rmsd1, rmsd2; // least total RMSD
44  std::vector<double> rmsdList1,
45  rmsdList2; // List of RMSD per atom in the order fed in
46  double scale1, scale2; // Scale factor
47  // Deformed block classification
48  bool doAngleCriterion = false; // For deformed blocks
49  // -----------------------
50  // Getting the target Eigen vectors
51  // Get the re-ordered matched basal rings, ordered with respect to each other
52  pntToPnt::relOrderPrismBlock(yCloud, *basal1, *basal2, nList, &matchedBasal1,
53  &matchedBasal2);
54  // -----------------------
55  // Match the basal rings with a complete prism block, given the relatively
56  // ordered basal rings This actually only needs to be done for deformed prism
57  // blocks
58  bool blockMatch = match::matchPrismBlock(
59  yCloud, nList, refPoints, &matchedBasal1, &matchedBasal2, &startingIndex);
60  // -----------------------
61  // Check for deformed prisms
62  if (!isPerfect) {
63  if (!blockMatch) {
64  return blockMatch;
65  }
66  }
67  // If the deformed prism does not fulfil the shape matching criterion, then do
68  // not calculate the RMSD per atom
69  // -----------------------
70  // Section for RMSD per atom, based on basal ring matching
71 
72  basal1Set =
73  pntToPnt::fillPointSetPrismRing(yCloud, matchedBasal1, startingIndex);
74  // Fill up the point set for basal2
75  basal2Set =
76  pntToPnt::fillPointSetPrismRing(yCloud, matchedBasal2, startingIndex);
77  // Use Horn's algorithm to calculate the absolute orientation and RMSD etc.
78  absor::hornAbsOrientation(refPoints, basal1Set, &quat1, &rmsd1, &rmsdList1,
79  &scale1); // basal1
80  absor::hornAbsOrientation(refPoints, basal2Set, &quat2, &rmsd2, &rmsdList2,
81  &scale2); // basal2
82  // // ------------
83  // // Update the per-atom RMSD for each particle
84  // // Basal1
85  // match::updatePerAtomRMSDRing(matchedBasal1, startingIndex, rmsdList1,
86  // rmsdPerAtom);
87  // // Basal2
88  // match::updatePerAtomRMSDRing(matchedBasal2, startingIndex, rmsdList2,
89  // rmsdPerAtom);
90  // // ------------
91  // ------------
92  // Update the RMSD (obtained for each ring) for each particle
93  // Basal1
94  match::updateRMSDRing(matchedBasal1, startingIndex, rmsd1, rmsdPerAtom);
95  // Basal2
96  match::updateRMSDRing(matchedBasal2, startingIndex, rmsd2, rmsdPerAtom);
97  // ------------
98 
99  return true;
100 } // end of function
101 
111  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
112  std::vector<std::vector<int>> nList, const Eigen::MatrixXd &refPoints,
113  std::vector<int> *basal1, std::vector<int> *basal2,
114  std::vector<double> *rmsdPerAtom) {
115  //
116  int ringSize = (*basal1).size(); // Number of nodes in each basal ring
117  std::vector<int> matchedBasal1,
118  matchedBasal2; // Re-ordered basal rings 1 and 2
119  int dim = 3; // Number of dimensions
120  // Matrices for the point sets of the target basal rings
121  Eigen::MatrixXd basal1Set(ringSize,
122  dim); // Point set for the first basal ring
123  Eigen::MatrixXd basal2Set(ringSize,
124  dim); // Point set for the second basal ring
125  int startingIndex; // Index in the basal rings from which the reference point
126  // set is matched
127  double angDist; // Calculated angular distance between the two basal rings
128  // Variables for the absolute orientation
129  std::vector<double> quat1, quat2; // quaternion rotation
130  double rmsd1, rmsd2; // least total RMSD
131  std::vector<double> rmsdList1,
132  rmsdList2; // List of RMSD per atom in the order fed in
133  double scale1, scale2; // Scale factor
134  // Deformed block classification
135  bool doAngleCriterion = false; // For deformed blocks
136 
137  // Getting the target Eigen vectors
138  // Get the re-ordered matched basal rings, ordered with respect to each other
139  pntToPnt::relOrderPrismBlock(yCloud, *basal1, *basal2, &matchedBasal1,
140  &matchedBasal2);
141  // -----------------------
142  // Match the basal rings with a complete prism block, given the relatively
143  // ordered basal rings This actually only needs to be done for deformed prism
144  // blocks
145  bool blockMatch = match::matchPrismBlock(
146  yCloud, nList, refPoints, &matchedBasal1, &matchedBasal2, &startingIndex);
147  // -----------------------
148  // Check to see if the prism matches the reference prism block
149  if (!blockMatch) {
150  return blockMatch;
151  }
152 
153  // If the deformed prism does not fulfil the shape matching criterion, then do
154  // not calculate the RMSD per atom
155  // -----------------------
156  // Section for RMSD per atom, based on basal ring matching
157 
158  basal1Set =
159  pntToPnt::fillPointSetPrismRing(yCloud, matchedBasal1, startingIndex);
160  // Fill up the point set for basal2
161  basal2Set =
162  pntToPnt::fillPointSetPrismRing(yCloud, matchedBasal2, startingIndex);
163  // Use Horn's algorithm to calculate the absolute orientation and RMSD etc.
164  absor::hornAbsOrientation(refPoints, basal1Set, &quat1, &rmsd1, &rmsdList1,
165  &scale1); // basal1
166  absor::hornAbsOrientation(refPoints, basal2Set, &quat2, &rmsd2, &rmsdList2,
167  &scale2); // basal2
168  // // Calculate the angular distance between basal1 and basal2
169  // angDist = gen::angDistDegQuaternions(quat1, quat2);
170  // // Check if the shapes are aligned
171  // if (angDist > cutoffAngle) {
172  // return false;
173  // } // If not aligned, it is not a prism block
174  // ------------
175  // Update the RMSD (obtained for each ring) for each particle
176  // Basal1
177  match::updateRMSDRing(matchedBasal1, startingIndex, rmsd1, rmsdPerAtom);
178  // Basal2
179  match::updateRMSDRing(matchedBasal2, startingIndex, rmsd2, rmsdPerAtom);
180  // ------------
181 
182  return true;
183 } // end of function
184 
186 int match::updatePerAtomRMSDRing(std::vector<int> basalRing, int startingIndex,
187  std::vector<double> rmsdFromMatch,
188  std::vector<double> *rmsdPerAtom) {
189  //
190  int atomIndex; // Atom particle index, in rmsdPerAtom
191  int ringSize = basalRing.size(); // Size of the basal ring
192  int index; // Corresponding index in the basal ring
193  double iRMSD; // Per-particle RMSD
194 
195  // -----------------
196  // Update the RMSD per particle
197  for (int i = 0; i < ringSize; i++) {
198  index = i + startingIndex; // Corresponding index in the basal ring
199  // Wrap-around
200  if (index >= ringSize) {
201  index -= ringSize;
202  } // end of wrap-around
203  //
204  // Get the atom index
205  atomIndex = basalRing[index];
206  // Get and update the RMSD per particle
207  iRMSD = rmsdFromMatch[i];
208  if ((*rmsdPerAtom)[atomIndex] == -1) {
209  (*rmsdPerAtom)[atomIndex] = iRMSD;
210  } // end of update of RMSD
211 
212  } // end of updating the RMSD per particle
213  // -----------------
214  // finito
215  return 0;
216 } // end of function
217 
220 int match::updateRMSDRing(std::vector<int> basalRing, int startingIndex,
221  double rmsdVal, std::vector<double> *rmsdPerAtom) {
222  //
223  int atomIndex; // Atom particle index, in rmsdPerAtom
224  int ringSize = basalRing.size(); // Size of the basal ring
225  int index; // Corresponding index in the basal ring
226 
227  // -----------------
228  // Update the RMSD per particle
229  for (int i = 0; i < ringSize; i++) {
230  index = i + startingIndex; // Corresponding index in the basal ring
231  // Wrap-around
232  if (index >= ringSize) {
233  index -= ringSize;
234  } // end of wrap-around
235  //
236  // Get the atom index
237  atomIndex = basalRing[index];
238  // The RMSD value to update with is rmsdVal
239  if ((*rmsdPerAtom)[atomIndex] == -1) {
240  (*rmsdPerAtom)[atomIndex] = rmsdVal;
241  } // end of update of RMSD
242 
243  } // end of updating the RMSD for each particle
244  // -----------------
245  // finito
246  return 0;
247 } // end of function
248 
252  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
253  std::vector<std::vector<int>> nList, const Eigen::MatrixXd &refPoints,
254  std::vector<int> *basal1, std::vector<int> *basal2, int *beginIndex) {
255  //
256  int ringSize = (*basal1).size(); // Number of nodes in the basal rings
257  bool isMatch; // Qualifier for whether the prism block matches or not
258  int dim = 3; // Number of dimensions
259  int nop = 2 * ringSize; // Number of particles in the prism block
260  Eigen::MatrixXd refPrismBlock(
261  nop, dim); // eigen matrix for the reference prism block
262  Eigen::MatrixXd targetPrismBlock(
263  nop, dim); // eigen matrix for the target prism block
264  int startingIndex; // Index from which the basal rings will be ordered
265  // (permutations)
266  // variables for shape-matching
267  std::vector<double> quat; // Quaternion for the rotation
268  double rmsd; // RMSD value for the entire shape
269  std::vector<double> rmsdList; // RMSD value for each point
270  double scale; // Scale factor
271  // ----------------------------------------------------
272  // Get the reference prism block
273  refPrismBlock =
274  pntToPnt::createPrismBlock(yCloud, refPoints, ringSize, *basal1, *basal2);
275  // ----------------------------------------------------
276 
277  // Loop through possible point-to-point correspondences
278  if (ringSize % 2 == 0 || ringSize == 3) {
279  startingIndex = 0;
280  // Fill up the point set for the target prism block
281  targetPrismBlock =
282  pntToPnt::fillPointSetPrismBlock(yCloud, *basal1, *basal2, 0);
283  // Shape-matching
284  absor::hornAbsOrientation(refPrismBlock, targetPrismBlock, &quat, &rmsd,
285  &rmsdList,
286  &scale); // basal2
287  } // even or if there are 3 nodes
288  else {
289  // Define the vector, RMSD etc:
290  std::vector<double> currentQuat; // quaternion rotation
291  double currentRmsd; // least total RMSD
293  currentRmsdList; // List of RMSD per atom in the order fed in
294  double currentScale;
295  // Loop through all possible startingIndex
296  for (int i = 0; i < ringSize; i++) {
297  //
298  // Fill up the point set for basal1
299  targetPrismBlock =
300  pntToPnt::fillPointSetPrismBlock(yCloud, *basal1, *basal2, i);
301  // Use Horn's algorithm to calculate the absolute orientation and RMSD
302  // etc. for basal1
303  absor::hornAbsOrientation(refPrismBlock, targetPrismBlock, &currentQuat,
304  &currentRmsd, &currentRmsdList, &currentScale);
305  // Comparison to get the least RMSD for the correct mapping
306  if (i == 0) {
307  // Init
308  quat = currentQuat;
309  rmsd = currentRmsd;
310  rmsdList = currentRmsdList;
311  scale = currentScale;
312  startingIndex = i;
313  } // init
314  else {
315  // Check to see if the calculated RMSD is less than the RMSD already
316  // saved
317  if (currentRmsd < rmsd) {
318  quat = currentQuat;
319  rmsd = currentRmsd;
320  rmsdList = currentRmsdList;
321  scale = currentScale;
322  startingIndex = i;
323  } // end of check to see if the current RMSD is smaller
324  } // end of comparison and filling
325  } // end of loop through startingIndex
326  } // ringSize is odd, so every point must be tried
327 
328  // Update the index from which matching is started
329  *beginIndex = startingIndex;
330 
331  // // TEST DELETE BLOCK LATER
332  // if (ringSize == 6) {
333  // std::fstream rmsdFile;
334  // rmsdFile.open("../runOne/topoINT/rmsd6.dat",
335  // std::fstream::in | std::fstream::out | std::fstream::app);
336  // rmsdFile << rmsd << "\n";
337  // rmsdFile.close();
338  // } else if (ringSize == 4) {
339  // std::fstream rmsdFile;
340  // rmsdFile.open("../runOne/topoINT/rmsd4.dat",
341  // std::fstream::in | std::fstream::out | std::fstream::app);
342  // rmsdFile << rmsd << "\n";
343  // rmsdFile.close();
344  // }
345  // // END OF BLOCK TO BE DELETED
346 
347  // Condition for shape-matching
348  if (rmsd <= 6) {
349  isMatch = true; // Is a prism block
350  } // within RMSD range
351  else {
352  isMatch = false;
353  }
354 
355  // Return
356  return isMatch;
357 }
int hornAbsOrientation(const Eigen::MatrixXd &refPoints, const Eigen::MatrixXd &targetPoints, std::vector< double > *quat, double *rmsd, std::vector< double > *rmsdList, double *scale)
Get the absolute orientation using Horn's algorithm (with quaternions)
bool matchPrismBlock(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, const Eigen::MatrixXd &refPoints, std::vector< int > *basal1, std::vector< int > *basal2, int *beginIndex)
Definition: shapeMatch.cpp:251
int updatePerAtomRMSDRing(std::vector< int > basalRing, int startingIndex, std::vector< double > rmsdFromMatch, std::vector< double > *rmsdPerAtom)
Update the per-particle RMSD for a prism block basal ring.
Definition: shapeMatch.cpp:186
bool matchUntetheredPrism(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, const Eigen::MatrixXd &refPoints, std::vector< int > *basal1, std::vector< int > *basal2, std::vector< double > *rmsdPerAtom)
Definition: shapeMatch.cpp:110
int updateRMSDRing(std::vector< int > basalRing, int startingIndex, double rmsdVal, std::vector< double > *rmsdPerAtom)
Definition: shapeMatch.cpp:220
bool matchPrism(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, const Eigen::MatrixXd &refPoints, std::vector< int > *basal1, std::vector< int > *basal2, std::vector< double > *rmsdPerAtom, bool isPerfect=true)
Definition: shapeMatch.cpp:21
Eigen::MatrixXd fillPointSetPrismRing(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< int > basalRing, int startingIndex)
Eigen::MatrixXd createPrismBlock(molSys::PointCloud< molSys::Point< double >, double > *yCloud, const Eigen::MatrixXd &refPoints, int ringSize, std::vector< int > basal1, std::vector< int > basal2)
int relOrderPrismBlock(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< int > basal1, std::vector< int > basal2, std::vector< std::vector< int >> nList, std::vector< int > *outBasal1, std::vector< int > *outBasal2)
Eigen::MatrixXd fillPointSetPrismBlock(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< int > basal1, std::vector< int > basal2, int startingIndex)
T size(T... args)
This contains a collection of points; contains information for a particular frame.
Definition: mol_sys.hpp:170
This contains per-particle information.
Definition: mol_sys.hpp:149