bop.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 <bop.hpp>
16 #include <iostream>
17 
18 namespace bg = boost::geometry;
19 
20 // //SPHERICAL HARMONIC FUNCTIONS
21 // /********************************************/ /**
22 // * Spherical harmonics using boost
23 // ***********************************************/
24 // std::vector<std::complex<double>>
25 // sph::spheriHarmo(int orderL, std::array<double, 2> radialCoord) {
26 // // Iterate over the index of order
27 // std::vector<int> v = {-3, -2, -1, 0, 1, 2, 3};
28 // // For keeping track of the index of the output vector
29 // int i(0);
30 // std::vector<std::complex<double>> result;
31 // for (auto n : v) {
32 // auto theta = radialCoord[1];
33 // auto phi = radialCoord[0];
34 // result.resize(result.size() + 1);
35 // // This is for l=3
36 // std::complex<double> b =
37 // boost::math::spherical_harmonic(orderL, n, theta, phi);
38 // result[i] = b;
39 // // Update the index
40 // i++;
41 // }
42 // return result;
43 // }
44 
58  // For keeping track of the index of the output vector
60  std::complex<double> b; // Boost temp value
61  int m;
62 
63  result.resize(2 * orderL + 1);
64 
65  for (int k = 0; k < 2 * orderL + 1; k++) {
66  double theta = radialCoord[1];
67  double phi = radialCoord[0];
68  m = k - orderL;
69  result[k] = boost::math::spherical_harmonic(orderL, m, theta, phi);
70  }
71 
72  return result;
73 }
74 
82  // The output
83  std::array<double, 2> result;
84  // Point Definitions
85  bg::model::point<long double, 3, bg::cs::cartesian> cartesianPoint;
86  bg::model::point<long double, 3, bg::cs::spherical<bg::radian>> azuPoint;
87  // Set Value (TODO: Recurse this)
88  bg::set<0>(cartesianPoint, cartCoord[0]);
89  bg::set<1>(cartesianPoint, cartCoord[1]);
90  bg::set<2>(cartesianPoint, cartCoord[2]);
91  // Transform
92  bg::transform(cartesianPoint, azuPoint);
93  result[0] = bg::get<0>(azuPoint);
94  result[1] = bg::get<1>(azuPoint);
95  return result;
96 }
97 
108  // For keeping track of the index of the output vector
110  double theta = angles[1];
111  double phi = angles[0];
112 
113  result.resize(7);
114 
115  for (int m = 0; m < 7; m++) {
116  result[m] = sph::lookupTableQ3(m, angles);
117  }
118 
119  return result;
120 }
121 
134  std::complex<double> result(0.0, 0.0);
135  const double pi = std::acos(-1);
136  const std::complex<double> i(0.0, 1.0);
137  double constant;
138  double theta = angles[1];
139  double phi = angles[0];
140 
141  if (m == 0) {
142  constant = 0.125 * std::sqrt(35 / pi);
143  result = constant * std::exp(-3.0 * i * phi) * std::pow(sin(theta), 3.0);
144  return result;
145  } else if (m == 1) {
146  constant = 0.25 * std::sqrt(105 / (2 * pi));
147  result = constant * std::exp(-2.0 * i * phi) * std::pow(sin(theta), 2.0) *
148  cos(theta);
149  return result;
150  } else if (m == 2) {
151  constant = 0.125 * std::sqrt(21 / pi);
152  result = constant * std::exp(-1.0 * i * phi) * sin(theta) *
153  (5.0 * std::pow(cos(theta), 2.0) - 1);
154  return result;
155  } else if (m == 3) {
156  constant = 0.25 * std::sqrt(7 / pi);
157  result = constant * (5.0 * std::pow(cos(theta), 3.0) - 3.0 * cos(theta));
158  return result;
159  } else if (m == 4) {
160  constant = -0.125 * std::sqrt(21 / pi);
161  result = constant * std::exp(i * phi) * sin(theta) *
162  (5.0 * std::pow(cos(theta), 2.0) - 1);
163  return result;
164  } else if (m == 5) {
165  constant = 0.25 * std::sqrt(105 / (2 * pi));
166  result = constant * std::exp(2.0 * i * phi) * std::pow(sin(theta), 2.0) *
167  cos(theta);
168  return result;
169  } else if (m == 6) {
170  constant = -0.125 * std::sqrt(35 / pi);
171  result = constant * std::exp(3.0 * i * phi) * std::pow(sin(theta), 3.0);
172  return result;
173  }
174 
175  return result;
176 }
177 
190  // For keeping track of the index of the output vector
192  double theta = angles[1];
193  double phi = angles[0];
194 
195  result.resize(13);
196 
197  for (int m = 0; m < 13; m++) {
198  result[m] = sph::lookupTableQ6(m, angles);
199  }
200 
201  return result;
202 }
203 
216  std::complex<double> result(0.0, 0.0);
217  const double pi = std::acos(-1);
218  const std::complex<double> i(0.0, 1.0);
219  double constant;
220  double theta = angles[1];
221  double phi = angles[0];
222 
223  if (m == 0) {
224  constant = 0.015625 * std::sqrt(3003 / pi);
225  result = constant * std::exp(-6.0 * i * phi) * std::pow(sin(theta), 6.0);
226  return result;
227  } else if (m == 1) {
228  constant = 0.09375 * std::sqrt(1001 / pi);
229  result = constant * std::exp(-5.0 * i * phi) * std::pow(sin(theta), 5.0) *
230  cos(theta);
231  return result;
232  } else if (m == 2) {
233  constant = 0.09375 * std::sqrt(91 / (2 * pi));
234  result = constant * std::exp(-4.0 * i * phi) * std::pow(sin(theta), 4.0) *
235  (11.0 * std::pow(cos(theta), 2.0) - 1);
236  return result;
237  } else if (m == 3) {
238  constant = 0.03125 * std::sqrt(1365 / pi);
239  result = constant * std::exp(-3.0 * i * phi) * std::pow(sin(theta), 3.0) *
240  (11.0 * std::pow(cos(theta), 3.0) - 3.0 * cos(theta));
241  return result;
242  } else if (m == 4) {
243  constant = 0.015625 * std::sqrt(1365 / pi);
244  result = constant * std::exp(-2.0 * i * phi) * std::pow(sin(theta), 2.0) *
245  (33.0 * std::pow(cos(theta), 4.0) -
246  18.0 * std::pow(cos(theta), 2.0) + 1.0);
247  return result;
248  } else if (m == 5) {
249  constant = 0.0625 * std::sqrt(273 / (2 * pi));
250  result = constant * std::exp(-1.0 * i * phi) * sin(theta) *
251  (33.0 * std::pow(cos(theta), 5.0) -
252  30.0 * std::pow(cos(theta), 3.0) + 5.0 * cos(theta));
253  return result;
254  } else if (m == 6) {
255  constant = 0.03125 * std::sqrt(13 / pi);
256  result = constant * (231.0 * std::pow(cos(theta), 6.0) -
257  315.0 * std::pow(cos(theta), 4.0) +
258  105.0 * std::pow(cos(theta), 2.0) - 5.0);
259  return result;
260  } else if (m == 7) {
261  constant = -0.0625 * std::sqrt(273 / (2 * pi));
262  result = constant * std::exp(i * phi) * sin(theta) *
263  (33.0 * std::pow(cos(theta), 5.0) -
264  30.0 * std::pow(cos(theta), 3.0) + 5.0 * cos(theta));
265  return result;
266  } else if (m == 8) {
267  constant = 0.015625 * std::sqrt(1365 / pi);
268  result = constant * std::exp(2.0 * i * phi) * std::pow(sin(theta), 2.0) *
269  (33.0 * std::pow(cos(theta), 4.0) -
270  18.0 * std::pow(cos(theta), 2.0) + 1.0);
271  return result;
272  } else if (m == 9) {
273  constant = -0.03125 * std::sqrt(1365 / pi);
274  result = constant * std::exp(3.0 * i * phi) * std::pow(sin(theta), 3.0) *
275  (11.0 * std::pow(cos(theta), 3.0) - 3.0 * cos(theta));
276  return result;
277  } else if (m == 10) {
278  constant = 0.09375 * std::sqrt(91 / (2 * pi));
279  result = constant * std::exp(4.0 * i * phi) * std::pow(sin(theta), 4.0) *
280  (11.0 * std::pow(cos(theta), 2.0) - 1);
281  return result;
282  } else if (m == 11) {
283  constant = -0.09375 * std::sqrt(1001 / pi);
284  result = constant * std::exp(5.0 * i * phi) * std::pow(sin(theta), 5.0) *
285  cos(theta);
286  return result;
287  } else if (m == 12) {
288  constant = 0.015625 * std::sqrt(3003 / pi);
289  result = constant * std::exp(6.0 * i * phi) * std::pow(sin(theta), 6.0);
290  return result;
291  }
292 
293  return result;
294 }
295 
300  std::vector<std::vector<int>> nList, bool isSlice) {
301  //
302  int l = 3; // TODO: Don't hard-code this; change later
303  int iatomID; // Atom ID (key) of iatom
304  int iatomIndex; // Index (value) of iatom
305  int jatomID; // Atom ID (key) of jatom
306  int jatomIndex; // Index (value) of nearest neighbour
307  std::array<double, 3> delta;
308  std::array<double, 2> angles;
309  chill::QlmAtom QlmTotal; // Qlm for each iatom
311  yl; // temp q_lm for each pair of iatom and jatom
312  std::complex<double> dot_product = {0, 0};
313  std::complex<double> qI = {0, 0};
314  std::complex<double> qJ = {0, 0};
315  std::complex<double> Inorm = {0, 0};
316  std::complex<double> Jnorm = {0, 0};
317  std::complex<double> complexDenominator = {0, 0};
318  std::complex<double> complexCij = {0, 0};
319  molSys::Result temp_cij; // Holds the c_ij value
320  double cij_real;
321  int nnumNeighbours; // Number of nearest neighbours for iatom
322 
323  QlmTotal.ptq.resize(yCloud->nop);
324 
325  // Loop through the neighbour list
326  for (int iatom = 0; iatom < nList.size(); iatom++) {
327  // The atom index is iatom
328  iatomIndex = iatom;
329  iatomID =
330  nList[iatomIndex][0]; // The first element in nList is the ID of iatom
331  nnumNeighbours = nList[iatomIndex].size() - 1;
332  // Now loop over the first four neighbours
333  for (int j = 1; j <= nnumNeighbours; j++) {
334  // Get the ID of jatom saved in the neighbour list
335  jatomID = nList[iatomIndex][j];
336 
337  // Get the index of jatom
338  auto it = yCloud->idIndexMap.find(jatomID);
339 
340  if (it != yCloud->idIndexMap.end()) {
341  jatomIndex = it->second;
342  } // found jatom
343  else {
344  std::cerr << "Something is wrong with the ID and index map.\n";
345  return *yCloud;
346  } // error handling
347 
348  // Get the relative distance now that the index values are known
349  delta = gen::relDist(yCloud, iatomIndex, jatomIndex);
350  double r = std::sqrt(std::pow(delta[0], 2.0) + std::pow(delta[1], 2.0) +
351  std::pow(delta[2], 2.0));
352  angles[1] = acos(delta[2] / r); // theta
353  angles[0] = atan2(delta[0], delta[1]); // phi
354 
355  // Now add over all nearest neighbours
356  if (j == 1) {
357  QlmTotal.ptq[iatomIndex].ylm = sph::spheriHarmo(3, angles);
358  // QlmTotal.ptq[iatom].ylm = sph::lookupTableQ3Vec(angles);
359  continue;
360  }
361  // Not for the first jatom
362  yl = sph::spheriHarmo(3, angles);
363  for (int m = 0; m < 2 * l + 1; m++) {
364  QlmTotal.ptq[iatomIndex].ylm[m] += yl[m];
365  // QlmTotal.ptq[iatom].ylm[m] += sph::lookupTableQ3(m, angles);
366  }
367  } // End of loop over 4 nearest neighbours
368 
369  // Divide by 4
370  QlmTotal.ptq[iatomIndex].ylm =
371  gen::avgVector(QlmTotal.ptq[iatom].ylm, l, nnumNeighbours);
372  } // End of looping over all iatom
373 
374  // ------------------------------------------------
375  // Now that you have all qlm for the particles,
376  // find c_ij
377  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
378  // if(yCloud->pts[iatom].type!=typeO){continue;}
379  // if this is a slice and the particle is not in the slice
380  // then skip
381  if (isSlice) {
382  if (yCloud->pts[iatom].inSlice == false) {
383  continue;
384  }
385  }
386  // The index is what we are looping through
387  iatomIndex = iatom;
388  nnumNeighbours = nList[iatomIndex].size() - 1;
389  yCloud->pts[iatomIndex].c_ij.reserve(nnumNeighbours);
390  // loop over the 4 nearest neighbours
391  for (int j = 1; j <= nnumNeighbours; j++) {
392  // Init to zero
393  dot_product = {0, 0};
394  Inorm = {0, 0};
395  Jnorm = {0, 0};
396  // Get ID of the nearest neighbour
397  jatomID = nList[iatomIndex][j];
398  // Get the index (value) from the ID (key)
399  auto it = yCloud->idIndexMap.find(jatomID);
400 
401  if (it != yCloud->idIndexMap.end()) {
402  jatomIndex = it->second;
403  } // found jatom
404  else {
405  std::cerr << "Something is wrong with the ID and index map.\n";
406  return *yCloud;
407  } // error handling
408  // Spherical harmonics
409  for (int m = 0; m < 2 * l + 1; m++) {
410  qI = QlmTotal.ptq[iatomIndex].ylm[m];
411  qJ = QlmTotal.ptq[jatomIndex].ylm[m];
412  dot_product = dot_product + (qI * std::conj(qJ)); // unnormalized
413  Inorm = Inorm + (qI * std::conj(qI));
414  Jnorm = Jnorm + (qJ * std::conj(qJ));
415  } // end loop over m components
416  // Get the denominator
417  complexDenominator = std::sqrt(Inorm * Jnorm);
418  complexCij = dot_product / complexDenominator;
419  // Update c_ij and type
420  cij_real = complexCij.real();
421  temp_cij.c_value = cij_real;
422  if (cij_real < -0.8) {
424  } else if (cij_real > -0.2 && cij_real < -0.05) {
426  } else {
428  }
429  yCloud->pts[iatomIndex].c_ij.push_back(temp_cij);
430  } // end loop over nearest neighbours
431  }
432 
433  return *yCloud;
434 }
435 
438  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
439  std::vector<std::vector<int>> nList, bool isSlice) {
440  int ih, ic, water, interIce, unknown, total; // No. of particles of each type
441  ih = ic = water = unknown = interIce = total = 0;
442  int num_staggrd, num_eclipsd, na;
443  molSys::bond_type bondType;
444  int nnumNeighbours; // Number of nearest neighbours
445 
446  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
447  // if(yCloud->pts[iatom].type!=typeO){continue;}
448  // if this is a slice and the particle is not in the slice
449  // then skip
450  if (isSlice) {
451  if (yCloud->pts[iatom].inSlice == false) {
452  continue;
453  }
454  }
455  total++; // Update the total number of atoms considered. Change this to
456  // check for slices
457  num_staggrd = num_eclipsd = na =
458  0; // init to zero before loop through neighbours
459 
460  nnumNeighbours = nList[iatom].size() - 1;
461  // Loop through the bond cij and get the number of staggered, eclipsed bonds
462  for (int j = 0; j < nnumNeighbours; j++) {
463  bondType = yCloud->pts[iatom].c_ij[j].classifier;
464  if (bondType == molSys::bond_type::eclipsed) {
465  num_eclipsd++;
466  } else if (bondType == molSys::bond_type::staggered) {
467  num_staggrd++;
468  } else {
469  na++;
470  }
471  } // End of loop through neighbours
472 
473  // Add more tests later
474  yCloud->pts[iatom].iceType = molSys::atom_state_type::unclassified; // default
475  // Cubic ice
476  // if (num_eclipsd==0 && num_staggrd==4){
477  // yCloud->pts[iatom].iceType = molSys::cubic;
478  // ic++;
479  // }
480  if (num_staggrd >= 4) {
481  yCloud->pts[iatom].iceType = molSys::atom_state_type::cubic;
482  ic++;
483  }
484  // Hexagonal
485  else if (num_eclipsd == 1 && num_staggrd == 3) {
486  yCloud->pts[iatom].iceType = molSys::atom_state_type::hexagonal;
487  ih++;
488  }
489  // Interfacial
490  else if (isInterfacial(yCloud, nList, iatom, num_staggrd, num_eclipsd)) {
491  yCloud->pts[iatom].iceType = molSys::atom_state_type::interfacial;
492  interIce++;
493  } else {
494  yCloud->pts[iatom].iceType = molSys::atom_state_type::water;
495  water++;
496  }
497 
498  } // End of loop through every iatom
499 
500  return *yCloud;
501 }
502 
507  int firstFrame, bool isSlice, std::string outputFileName) {
508  int ih, ic, water, interIce, unknown, total; // No. of particles of each type
509  ih = ic = water = unknown = interIce = total = 0;
510  int num_staggrd, num_eclipsd, na;
511  molSys::bond_type bondType;
512  int nnumNeighbours; // Number of nearest neighbours
513 
514  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
515  // if(yCloud->pts[iatom].type!=typeO){continue;}
516  // if this is a slice and the particle is not in the slice
517  // then skip
518  if (isSlice) {
519  if (yCloud->pts[iatom].inSlice == false) {
520  continue;
521  }
522  }
523  total++; // Update the total number of atoms considered. Change this to
524  // check for slices
525  num_staggrd = num_eclipsd = na =
526  0; // init to zero before loop through neighbours
527 
528  nnumNeighbours = nList[iatom].size() - 1;
529  // Loop through the bond cij and get the number of staggered, eclipsed bonds
530  for (int j = 0; j < nnumNeighbours; j++) {
531  bondType = yCloud->pts[iatom].c_ij[j].classifier;
532  if (bondType == molSys::bond_type::eclipsed) {
533  num_eclipsd++;
534  } else if (bondType == molSys::bond_type::staggered) {
535  num_staggrd++;
536  } else {
537  na++;
538  }
539  } // End of loop through neighbours
540 
541  // Add more tests later
542  yCloud->pts[iatom].iceType = molSys::atom_state_type::unclassified; // default
543  // Cubic ice
544  // if (num_eclipsd==0 && num_staggrd==4){
545  // yCloud->pts[iatom].iceType = molSys::cubic;
546  // ic++;
547  // }
548  if (num_staggrd >= 4) {
549  yCloud->pts[iatom].iceType = molSys::atom_state_type::cubic;
550  ic++;
551  }
552  // Hexagonal
553  else if (num_eclipsd == 1 && num_staggrd == 3) {
554  yCloud->pts[iatom].iceType = molSys::atom_state_type::hexagonal;
555  ih++;
556  }
557  // Interfacial
558  else if (isInterfacial(yCloud, nList, iatom, num_staggrd, num_eclipsd)) {
559  yCloud->pts[iatom].iceType = molSys::atom_state_type::interfacial;
560  interIce++;
561  } else {
562  yCloud->pts[iatom].iceType = molSys::atom_state_type::water;
563  water++;
564  }
565 
566  } // End of loop through every iatom
567 
568  // water = total - ic -ih;
569 
570  // --------------------
571  // Create the directories if needed
572  sout::makePath(path);
573  std::string outputDirName = path + "bop";
574  sout::makePath(outputDirName);
575  // --------------------
576 
577  // Print to file
578  std::ofstream outputFile;
579  outputFile.open(path + "bop/" + outputFileName, std::ios_base::app);
580  // --------------------
581  // Write out the comment line for the first frame
582  if (yCloud->currentFrame == firstFrame) {
583  outputFile << "Frame Ic Ih Interfacial Water Total \n";
584  }
585  // --------------------
586  outputFile << yCloud->currentFrame << " " << ic << " " << ih << " "
587  << interIce << " " << water << " " << total << "\n";
588  outputFile.close();
589 
590  return *yCloud;
591 }
592 
603  std::vector<std::vector<int>> nList, bool isSlice) {
604  //
605  int l = 3; // TODO: Don't hard-code this; change later
606  int iatomID; // Atom ID (key) of iatom
607  int iatomIndex; // Index (value) of iatom
608  int jatomID; // Atom ID (key) of jatom
609  int jatomIndex; // Index (value) of nearest neighbour
610  std::array<double, 3> delta;
611  std::array<double, 2> angles;
612  chill::QlmAtom QlmTotal; // Qlm for each iatom
614  yl; // temp q_lm for each pair of iatom and jatom
615  std::complex<double> dot_product = {0, 0};
616  std::complex<double> qI = {0, 0};
617  std::complex<double> qJ = {0, 0};
618  std::complex<double> Inorm = {0, 0};
619  std::complex<double> Jnorm = {0, 0};
620  std::complex<double> complexDenominator = {0, 0};
621  std::complex<double> complexCij = {0, 0};
622  molSys::Result temp_cij; // Holds the c_ij value
623  double cij_real;
624  int nnumNeighbours; // Number of nearest neighbours for iatom
625 
626  QlmTotal.ptq.resize(yCloud->nop);
627 
628  // Loop through the neighbour list
629  for (int iatom = 0; iatom < nList.size(); iatom++) {
630  // The atom index is iatom
631  iatomIndex = iatom;
632  iatomID =
633  nList[iatomIndex][0]; // The first element in nList is the ID of iatom
634  nnumNeighbours = nList[iatomIndex].size() - 1;
635  // Now loop over the first four neighbours
636  for (int j = 1; j <= nnumNeighbours; j++) {
637  // Get the ID of jatom saved in the neighbour list
638  jatomID = nList[iatomIndex][j];
639 
640  // Get the index of jatom
641  auto it = yCloud->idIndexMap.find(jatomID);
642 
643  if (it != yCloud->idIndexMap.end()) {
644  jatomIndex = it->second;
645  } // found jatom
646  else {
647  std::cerr << "Something is wrong with the ID and index map.\n";
648  return *yCloud;
649  } // error handling
650 
651  // Get the relative distance now that the index values are known
652  delta = gen::relDist(yCloud, iatomIndex, jatomIndex);
653  double r = std::sqrt(std::pow(delta[0], 2.0) + std::pow(delta[1], 2.0) +
654  std::pow(delta[2], 2.0));
655  angles[1] = acos(delta[2] / r); // theta
656  angles[0] = atan2(delta[0], delta[1]); // phi
657 
658  // Now add over all nearest neighbours
659  if (j == 1) {
660  QlmTotal.ptq[iatomIndex].ylm = sph::spheriHarmo(3, angles);
661  // QlmTotal.ptq[iatom].ylm = sph::lookupTableQ3Vec(angles);
662  continue;
663  }
664  // Not for the first jatom
665  yl = sph::spheriHarmo(3, angles);
666  for (int m = 0; m < 2 * l + 1; m++) {
667  QlmTotal.ptq[iatomIndex].ylm[m] += yl[m];
668  // QlmTotal.ptq[iatom].ylm[m] += sph::lookupTableQ3(m, angles);
669  }
670  } // End of loop over 4 nearest neighbours
671 
672  // Divide by 4
673  QlmTotal.ptq[iatomIndex].ylm =
674  gen::avgVector(QlmTotal.ptq[iatom].ylm, l, nnumNeighbours);
675  } // End of looping over all iatom
676 
677  // ------------------------------------------------
678  // Now that you have all qlm for the particles,
679  // find c_ij
680  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
681  // if(yCloud->pts[iatom].type!=typeO){continue;}
682  // if this is a slice and the particle is not in the slice
683  // then skip
684  if (isSlice) {
685  if (yCloud->pts[iatom].inSlice == false) {
686  continue;
687  }
688  }
689  // The index is what we are looping through
690  iatomIndex = iatom;
691  nnumNeighbours = nList[iatomIndex].size() - 1;
692  yCloud->pts[iatomIndex].c_ij.reserve(nnumNeighbours);
693  // loop over the 4 nearest neighbours
694  for (int j = 1; j <= nnumNeighbours; j++) {
695  // Init to zero
696  dot_product = {0, 0};
697  Inorm = {0, 0};
698  Jnorm = {0, 0};
699  // Get ID of the nearest neighbour
700  jatomID = nList[iatomIndex][j];
701  // Get the index (value) from the ID (key)
702  auto it = yCloud->idIndexMap.find(jatomID);
703 
704  if (it != yCloud->idIndexMap.end()) {
705  jatomIndex = it->second;
706  } // found jatom
707  else {
708  std::cerr << "Something is wrong with the ID and index map.\n";
709  return *yCloud;
710  } // error handling
711  // Spherical harmonics
712  for (int m = 0; m < 2 * l + 1; m++) {
713  qI = QlmTotal.ptq[iatomIndex].ylm[m];
714  qJ = QlmTotal.ptq[jatomIndex].ylm[m];
715  dot_product = dot_product + (qI * std::conj(qJ)); // unnormalized
716  Inorm = Inorm + (qI * std::conj(qI));
717  Jnorm = Jnorm + (qJ * std::conj(qJ));
718  } // end loop over m components
719  // Get the denominator
720  complexDenominator = std::sqrt(Inorm * Jnorm);
721  complexCij = dot_product / complexDenominator;
722  // Update c_ij and type
723  cij_real = complexCij.real();
724  temp_cij.c_value = cij_real;
725  if (cij_real <= -0.8) {
727  } else if (cij_real >= -0.35 && cij_real <= 0.25) {
729  } else {
731  }
732  yCloud->pts[iatomIndex].c_ij.push_back(temp_cij);
733  } // end loop over nearest neighbours
734  }
735 
736  // ------------------------------------------------
737 
738  return *yCloud;
739 }
740 
757  int firstFrame, bool isSlice,
758  std::string outputFileName) {
759  int ih, ic, interIce, water, unknown, clath, interClath,
760  total; // No. of particles of each type
761  ih = ic = water = unknown = interIce = total = 0;
762  clath = interClath = 0;
763  int num_staggrd, num_eclipsd, na;
764  molSys::bond_type bondType;
765  int nnumNeighbours; // number of nearest neighbours
766 
767  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
768  // if(yCloud->pts[iatom].type!=typeO){continue;}
769  // if this is a slice and the particle is not in the slice
770  // then skip
771  if (isSlice) {
772  if (yCloud->pts[iatom].inSlice == false) {
773  continue;
774  }
775  }
776  total++; // Update the total number of atoms considered. Change this to a
777  // check for slices
778  nnumNeighbours =
779  nList[iatom].size() - 1; // number of nearest neighbours (total -1)
780  num_staggrd = num_eclipsd = na =
781  0; // init to zero before loop through neighbours
782  // Loop through the bond cij and get the number of staggered, eclipsed bonds
783  for (int j = 0; j < nnumNeighbours; j++) {
784  bondType = yCloud->pts[iatom].c_ij[j].classifier;
785  if (bondType == molSys::bond_type::eclipsed) {
786  num_eclipsd++;
787  } else if (bondType == molSys::bond_type::staggered) {
788  num_staggrd++;
789  } else {
790  na++;
791  }
792  } // End of loop through neighbours
793 
794  // Add more tests later
795  yCloud->pts[iatom].iceType = molSys::atom_state_type::unclassified; // default
796  if (nnumNeighbours == 4) {
797  // Cubic ice
798  if (num_eclipsd == 0 && num_staggrd == 4) {
799  yCloud->pts[iatom].iceType = molSys::atom_state_type::cubic;
800  ic++;
801  }
802  // Hexagonal
803  else if (num_eclipsd == 1 && num_staggrd == 3) {
804  yCloud->pts[iatom].iceType = molSys::atom_state_type::hexagonal;
805  ih++;
806  }
807  // Interfacial
808  else if (isInterfacial(yCloud, nList, iatom, num_staggrd, num_eclipsd)) {
809  yCloud->pts[iatom].iceType = molSys::atom_state_type::interfacial;
810  interIce++;
811  }
812  // Clathrate
813  else if (num_eclipsd == 4 && num_staggrd == 0) {
814  yCloud->pts[iatom].iceType = molSys::atom_state_type::clathrate;
815  clath++;
816  }
817  // Interfacial clathrate
818  else if (num_eclipsd == 3) {
819  yCloud->pts[iatom].iceType = molSys::atom_state_type::interClathrate;
820  interClath++;
821  }
822  // Water
823  else {
824  yCloud->pts[iatom].iceType = molSys::atom_state_type::water;
825  water++;
826  }
827  } else {
828  yCloud->pts[iatom].iceType = molSys::atom_state_type::water;
829  water++;
830  }
831 
832  } // End of loop through every iatom
833 
834  // water = total - ic -ih;
835 
836  // --------------------
837  // Create the directories if needed
838  sout::makePath(path);
839  std::string outputDirName = path + "bop";
840  sout::makePath(outputDirName);
841  // --------------------
842 
843  std::ofstream outputFile;
844  outputFile.open(path + "bop/" + outputFileName, std::ios_base::app);
845  // --------------------
846  // Comment line for the first line
847  if (yCloud->currentFrame == firstFrame) {
848  outputFile << "Frame Ic Ih Interfacial Clath InterClath Water Total\n";
849  }
850  // --------------------
851  outputFile << yCloud->currentFrame << " " << ic << " " << ih << " "
852  << interIce << " " << clath << " " << interClath << " " << water
853  << " " << total << "\n";
854  outputFile.close();
855 
856  return *yCloud;
857 }
858 
859 // TODO: Add code for slices!
869  std::vector<std::vector<int>> nList, bool isSlice) {
870  //
871  int l = 6; // We're using q6 here
872  int jatomID; // Atom ID of the nearest neighbour
873  int jatomIndex; // Index of nearest neighbour
874  std::array<double, 3> delta;
875  std::array<double, 2> angles;
876  chill::QlmAtom QlmTotal; // Qlm for each iatom
878  yl; // temp q_lm for each pair of iatom and jatom
879  std::complex<double> dot_product = {0, 0};
880  std::complex<double> qI = {0, 0};
881  std::complex<double> qJ = {0, 0};
882  std::complex<double> Inorm = {0, 0};
883  std::complex<double> Jnorm = {0, 0};
884  std::complex<double> complexDenominator = {0, 0};
885  std::complex<double> complexCij = {0, 0};
886  double cij_real;
887  int nnumNeighbours;
888  std::vector<double> resultQ; // Vector with averaged q values
889  double q_value = 0.0; // Averaged q value per neighbour pair
890 
891  QlmTotal.ptq.resize(yCloud->nop);
892  resultQ.resize(yCloud->nop);
893 
894  // Loop through every index in yCloud
895  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
896  // if(yCloud->pts[iatom].type!=typeO){continue;}
897 
898  nnumNeighbours = nList[iatom].size() - 1; // One less than the actual length
899  // Now loop over the first four neighbours
900  for (int j = 1; j <= nnumNeighbours; j++) {
901  // Get the atom ID
902  jatomID = nList[iatom][j]; // Atom ID (key)
903 
904  // Get the atom index (value) from the atom ID (key)
905  auto it = yCloud->idIndexMap.find(jatomID);
906 
907  if (it != yCloud->idIndexMap.end()) {
908  jatomIndex = it->second;
909  } else {
910  std::cerr << "Your map must be wrong.\n";
911  return resultQ; // return with error
912  }
913 
914  // Get the relative distances
915  delta = gen::relDist(yCloud, iatom, jatomIndex);
916 
917  // angles = sph::radialCoord(delta);
918  double r = std::sqrt(std::pow(delta[0], 2.0) + std::pow(delta[1], 2.0) +
919  std::pow(delta[2], 2.0));
920  angles[1] = acos(delta[2] / r); // theta
921  angles[0] = atan2(delta[0], delta[1]); // phi
922 
923  // Now add over all nearest neighbours
924  if (j == 1) {
925  // QlmTotal.ptq[iatom].ylm = sph::spheriHarmo(l, angles);
926  QlmTotal.ptq[iatom].ylm = sph::lookupTableQ6Vec(angles);
927  continue;
928  }
929  // Not for the first jatom
930  yl = sph::spheriHarmo(l, angles);
931  for (int m = 0; m < 2 * l + 1; m++) {
932  QlmTotal.ptq[iatom].ylm[m] += yl[m];
933  // QlmTotal.ptq[iatom].ylm[m] += sph::lookupTableQ6(m, angles);
934  }
935  } // End of loop over 4 nearest neighbours
936 
937  // Divide by 4
938  QlmTotal.ptq[iatom].ylm =
939  gen::avgVector(QlmTotal.ptq[iatom].ylm, l, nnumNeighbours);
940  } // End of looping over all iatom
941 
942  // ------------------------------------------------
943  // Now that you have all qlm for the particles,
944  // find c_ij
945  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
946  // if(yCloud->pts[iatom].type!=typeO){continue;}
947  // if this is a slice and the particle is not in the slice
948  // then skip TODO: UNCOMMENT
949  // if(isSlice){
950  // if(yCloud->pts[iatom].inSlice==false){continue;}
951  // }
952 
953  nnumNeighbours = nList[iatom].size() - 1; // Number of nearest neighbours
954  q_value = 0.0; // initialize to zero
955  // yCloud->pts[iatom].c_ij.reserve(nnumNeighbours);
956  // loop over the 4 nearest neighbours
957  for (int j = 1; j <= nnumNeighbours; j++) {
958  // Init to zero
959  dot_product = {0, 0};
960  Inorm = {0, 0};
961  Jnorm = {0, 0};
962  // Get index of the nearest neighbour!
963  jatomID = nList[iatom][j]; // Atom ID (key)
964 
965  // Get the index jatomIndex
966  auto it = yCloud->idIndexMap.find(jatomID);
967 
968  if (it != yCloud->idIndexMap.end()) {
969  jatomIndex = it->second;
970  } // end of getting the index of jatom
971 
972  for (int m = 0; m < 2 * l + 1; m++) {
973  qI = QlmTotal.ptq[iatom].ylm[m];
974  qJ = QlmTotal.ptq[jatomIndex].ylm[m];
975  dot_product = dot_product + (qI * std::conj(qJ)); // unnormalized
976  Inorm = Inorm + (qI * std::conj(qI));
977  Jnorm = Jnorm + (qJ * std::conj(qJ));
978  } // end loop over m components
979  // Get the denominator
980  complexDenominator = std::sqrt(Inorm * Jnorm);
981  complexCij = dot_product / complexDenominator;
982  // Update c_ij and type
983  cij_real = complexCij.real();
984 
985  q_value += cij_real;
986 
987  } // end loop over nearest neighbours
988 
989  // Average q_value over all nearest neighbours
990  q_value /= (double)nnumNeighbours;
991 
992  resultQ[iatom] = q_value; // Update the vector of averaged q6
993  }
994 
995  // ------------------------------------------------
996 
997  return resultQ;
998 }
999 
1012  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
1013  std::vector<double> *q6) {
1014  // If averaged q6 > 0.5, then consider it to be ice
1015  // If averaged q3 < -0.75 then it is ih or ic. If q3 < -0.85 then it is cubic,
1016  // otherwise it is hexagonal
1017  double avgQ3 = 0.0;
1018  int nnumNeighbours;
1019 
1020  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
1021  // Check if it has been classified as water
1022  if (yCloud->pts[iatom].iceType == molSys::atom_state_type::water) {
1023  if ((*q6)[iatom] > 0.5) {
1024  avgQ3 = 0.0; // init to zero
1025  // Loop through all c_ij
1026  nnumNeighbours = yCloud->pts[iatom].c_ij.size();
1027  for (int j = 0; j < nnumNeighbours; j++) {
1028  avgQ3 += yCloud->pts[iatom].c_ij[j].c_value;
1029  }
1030  avgQ3 /= (double)nnumNeighbours;
1031 
1032  // If averaged q3 < -0.75, then reclassify
1033  if (avgQ3 <= -0.75) {
1034  if (avgQ3 < -0.85) {
1035  yCloud->pts[iatom].iceType = molSys::atom_state_type::reCubic;
1036  } // molSys::cubic
1037  else {
1038  yCloud->pts[iatom].iceType = molSys::atom_state_type::reHex;
1039  } // molSys::hexagonal
1040  } // end of reclassification
1041  } // check for solid atom!
1042  } // end of check for water
1043  } // End loop through every iatom
1044 
1045  return *yCloud;
1046 }
1047 
1061  molSys::PointCloud<molSys::Point<double>, double> *yCloud, std::string path,
1062  int firstFrame, bool isSlice, std::string outputFileName) {
1063  int ih, ic, interIce, water, unknown, clath, interClath,
1064  total; // No. of particles of each type
1065  ih = ic = water = unknown = interIce = total = 0;
1066  clath = interClath = 0;
1067 
1068  for (int iatom = 0; iatom < yCloud->nop; iatom++) {
1069  // if(yCloud->pts[iatom].type != typeO){continue;}
1070  // check for slice
1071  if (isSlice) {
1072  if (yCloud->pts[iatom].inSlice == false) {
1073  continue;
1074  }
1075  }
1076  total++;
1077  if (yCloud->pts[iatom].iceType == molSys::atom_state_type::cubic) {
1078  ic++;
1079  } else if (yCloud->pts[iatom].iceType == molSys::atom_state_type::hexagonal) {
1080  ih++;
1081  } else if (yCloud->pts[iatom].iceType == molSys::atom_state_type::water) {
1082  water++;
1083  } else if (yCloud->pts[iatom].iceType == molSys::atom_state_type::interfacial) {
1084  interIce++;
1085  } else if (yCloud->pts[iatom].iceType == molSys::atom_state_type::clathrate) {
1086  clath++;
1087  } else if (yCloud->pts[iatom].iceType == molSys::atom_state_type::interClathrate) {
1088  interClath++;
1089  } else {
1090  unknown++;
1091  }
1092  }
1093 
1094  // --------------------
1095  // Create the directories if needed
1096  sout::makePath(path);
1097  std::string outputDirName = path + "bop";
1098  sout::makePath(outputDirName);
1099  // --------------------
1100  // Print to file
1101  std::ofstream outputFile;
1102  outputFile.open(path + "bop/" + outputFileName, std::ios_base::app);
1103  // --------------------
1104  // Write out the comment line
1105  if (yCloud->currentFrame == firstFrame) {
1106  outputFile << "Frame Ic Ih Interfacial Clath InterClath Water Total\n";
1107  }
1108  // --------------------
1109  outputFile << yCloud->currentFrame << " " << ic << " " << ih << " "
1110  << interIce << " " << clath << " " << interClath << " " << water
1111  << " " << total << "\n";
1112  outputFile.close();
1113 
1114  return 0;
1115 }
1116 
1130  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
1131  std::vector<std::vector<int>> nList, int iatom, int num_staggrd,
1132  int num_eclipsd) {
1133  int nnumNeighbours =
1134  nList[iatom].size() - 1; // number of nearest neighbours of iatom
1135  int neighStaggered =
1136  0; // number of staggered bonds in the neighbours of iatom
1137  int jatomID; // ID of the nearest neighbour
1138  int jatomIndex; // Index (value) of nearest neighbour
1139 
1140  // INTERFACIAL
1141  // Condition 1 : only two staggered bonds and at least
1142  // one neighbor with more than two staggered bonds
1143  if (num_staggrd == 2) {
1144  // Loop over the nearest neighbours
1145  for (int j = 1; j <= nnumNeighbours; j++) {
1146  // Get index of the nearest neighbour
1147  jatomID = nList[iatom][j];
1148  // Get the index (value) from the ID (key)
1149  auto it = yCloud->idIndexMap.find(jatomID);
1150 
1151  if (it != yCloud->idIndexMap.end()) {
1152  jatomIndex = it->second;
1153  } else {
1154  std::cerr << "Something is gravely wrong with your map.\n";
1155  return false;
1156  }
1157  //
1158  neighStaggered = chill::numStaggered(yCloud, nList, jatomIndex);
1159  if (neighStaggered > 2) {
1160  return true;
1161  }
1162  } // End loop over nearest neighbours
1163  } // end condition 1
1164  // Condition 2 : three staggered bonds, no eclipsed bond,
1165  // and at least one neighbor with two staggered bonds
1166  if (num_staggrd == 3 && num_eclipsd == 0) {
1167  // Loop over the nearest neighbours
1168  for (int j = 1; j <= nnumNeighbours; j++) {
1169  // Get index of the nearest neighbour
1170  // ID of the nearest neighbour
1171  jatomID = nList[iatom][j];
1172  // Get the index (value) from the ID (key)
1173  auto it = yCloud->idIndexMap.find(jatomID);
1174 
1175  if (it != yCloud->idIndexMap.end()) {
1176  jatomIndex = it->second;
1177  } else {
1178  std::cerr << "Something is gravely wrong with your map.\n";
1179  return false;
1180  }
1181  //
1182  neighStaggered = chill::numStaggered(yCloud, nList, jatomIndex);
1183  if (neighStaggered == 2) {
1184  return true;
1185  }
1186  }
1187  }
1188  // not interfacial
1189  return false;
1190 }
1191 
1202  molSys::PointCloud<molSys::Point<double>, double> *yCloud,
1203  std::vector<std::vector<int>> nList, int jatom) {
1204  int num_staggrd = 0; // Number of staggered bonds
1205  molSys::bond_type bondType; // Bond type
1206  int num_bonds; // No. of bonds of the jatom
1207  int nnumNeighbours =
1208  nList[jatom].size() - 1; // No. of nearest neighbours of index jatom
1209 
1210  // Loop over all bonds
1211  for (int i = 0; i < nnumNeighbours; i++) {
1212  bondType = yCloud->pts[jatom].c_ij[i].classifier;
1213  // If the bond is staggered increment the number of staggered bonds
1214  if (bondType == molSys::bond_type::staggered) {
1215  num_staggrd++;
1216  }
1217  } // end of loop over c_ij
1218 
1219  return num_staggrd;
1220 }
T acos(T... args)
File for the bond order parameter analysis.
T close(T... args)
T exp(T... args)
std::vector< std::complex< double > > lookupTableQ6Vec(std::array< double, 2 > angles)
Lookup table for Q6.
Definition: bop.cpp:189
std::vector< double > getq6(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, bool isSlice=false)
Definition: bop.cpp:868
std::vector< std::complex< double > > lookupTableQ3Vec(std::array< double, 2 > angles)
Lookup table for Q3.
Definition: bop.cpp:107
molSys::PointCloud< molSys::Point< double >, double > getCorrelPlus(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, bool isSlice=false)
Gets c_ij and then classifies bond types according to the CHILL+ algorithm.
Definition: bop.cpp:602
molSys::PointCloud< molSys::Point< double >, double > getIceTypePlus(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, std::string path, int firstFrame, bool isSlice=false, std::string outputFileName="chillPlus.txt")
Classifies each atom according to the CHILL+ algorithm.
Definition: bop.cpp:755
std::vector< YlmAtom > ptq
Definition: bop.hpp:170
molSys::PointCloud< molSys::Point< double >, double > getCorrel(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, bool isSlice=false)
Definition: bop.cpp:299
std::array< double, 2 > radialCoord(std::array< double, 3 > cartCoord)
Definition: bop.cpp:81
int printIceType(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::string path, int firstFrame, bool isSlice=false, std::string outputFileName="superChill.txt")
Prints out the iceType for a particular frame onto the terminal.
Definition: bop.cpp:1060
std::complex< double > lookupTableQ6(int m, std::array< double, 2 > angles)
Lookup table for Q6 (m=0 to m=12)
Definition: bop.cpp:215
bool isInterfacial(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, int iatom, int num_staggrd, int num_eclipsd)
Definition: bop.cpp:1129
std::vector< std::complex< double > > spheriHarmo(int orderL, std::array< double, 2 > radialCoord)
Definition: bop.cpp:57
int numStaggered(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, int jatom)
Finds the number of staggered bonds for a given atom of index jatom.
Definition: bop.cpp:1201
std::complex< double > lookupTableQ3(int m, std::array< double, 2 > angles)
Lookup table for Q3 (m=0 to m=6)
Definition: bop.cpp:133
molSys::PointCloud< molSys::Point< double >, double > getIceTypeNoPrint(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, bool isSlice=false)
Classifies each atom according to the CHILL algorithm without printing.
Definition: bop.cpp:437
molSys::PointCloud< molSys::Point< double >, double > reclassifyWater(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< double > *q6)
Definition: bop.cpp:1011
molSys::PointCloud< molSys::Point< double >, double > getIceType(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int >> nList, std::string path, int firstFrame, bool isSlice=false, std::string outputFileName="chill.txt")
Classifies each atom according to the CHILL algorithm.
Definition: bop.cpp:505
std::vector< std::complex< double > > avgVector(std::vector< std::complex< double >> v, int l, int neigh)
Definition: generic.hpp:313
std::array< double, 3 > relDist(molSys::PointCloud< molSys::Point< double >, double > *yCloud, int iatom, int jatom)
Definition: generic.hpp:200
const double pi
Definition: generic.hpp:54
bond_type
Definition: mol_sys.hpp:75
bond_type classifier
Definition: mol_sys.hpp:133
double c_value
Classifier according to CHILL, CHILL+ etc.
Definition: mol_sys.hpp:134
@ hexagonal
Ih, or particle type signifying Hexagonal Ice.
@ reCubic
Reclassified as cubic ice, according to the order parameter.
@ interfacial
Interfacial ice: ice-like molecules which do not fulfill the strict criteria of the Ic or Ih phases.
@ cubic
Ic, or particle type signifying Cubic Ice.
@ interClathrate
Interfacial clathrate ice phase.
@ water
Liquid/amorphous phase.
@ reHex
Reclassified as hexagonal ice, according to the order parameter.
@ clathrate
Clathrate ice phase.
@ unclassified
Not classified into any other category.
@ eclipsed
The bond is an eclipsed bond.
@ out_of_range
The bond cannot be classified as either staggered or eclipsed.
@ staggered
The bond is a staggered bond, according to the or value.
int makePath(const std::string &path)
T open(T... args)
T pow(T... args)
T real(T... args)
T resize(T... args)
T sqrt(T... args)
This is the local orientational bond order parameter , of length .
Definition: bop.hpp:169
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
This contains the bond classifier of enum class type bond_type, and the bond correlation factor.
Definition: mol_sys.hpp:132