bop.cpp
Go to the documentation of this file.
Code
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
18namespace 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
56std::vector<std::complex<double>>
57sph::spheriHarmo(int orderL, std::array<double, 2> radialCoord) {
58 // For keeping track of the index of the output vector
59 std::vector<std::complex<double>> result;
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
81std::array<double, 2> sph::radialCoord(std::array<double, 3> cartCoord) {
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
106std::vector<std::complex<double>>
107sph::lookupTableQ3Vec(std::array<double, 2> angles) {
108 // For keeping track of the index of the output vector
109 std::vector<std::complex<double>> result;
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
133std::complex<double> sph::lookupTableQ3(int m, std::array<double, 2> angles) {
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
188std::vector<std::complex<double>>
189sph::lookupTableQ6Vec(std::array<double, 2> angles) {
190 // For keeping track of the index of the output vector
191 std::vector<std::complex<double>> result;
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
215std::complex<double> sph::lookupTableQ6(int m, std::array<double, 2> angles) {
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
310 std::vector<std::complex<double>>
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
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
506 std::vector<std::vector<int>> nList, std::string path,
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
613 std::vector<std::complex<double>>
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
756 std::vector<std::vector<int>> nList, std::string path,
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!
867std::vector<double>
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
877 std::vector<std::complex<double>>
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
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
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
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}
File for the bond order parameter analysis.
std::vector< std::complex< double > > lookupTableQ6Vec(std::array< double, 2 > angles)
Lookup table for Q6.
Definition bop.cpp:189
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 > 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
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::vector< YlmAtom > ptq
Definition bop.hpp:170
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
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 > 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
std::vector< std::complex< double > > spheriHarmo(int orderL, std::array< double, 2 > radialCoord)
Definition bop.cpp:57
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::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 > reclassifyWater(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< double > *q6)
Definition bop.cpp:1011
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< double > getq6(molSys::PointCloud< molSys::Point< double >, double > *yCloud, std::vector< std::vector< int > > nList, bool isSlice=false)
Definition bop.cpp:868
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< 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
std::vector< S > pts
Definition mol_sys.hpp:171
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)
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