hips
Loading...
Searching...
No Matches
hips.cc
Go to the documentation of this file.
1
18
19#include "hips.h"
20
21#ifdef REACTIONS_ENABLED
22#include "batchReactor_cvode.h"
24#endif
25
26#include "randomGenerator.h"
27
28#include <iostream>
29#include <iomanip>
30#include <fstream>
31#include <sstream>
32#include <cmath>
33#include <vector>
34#include <algorithm>
35
36using namespace std;
37
39hips::hips(int nLevels_,
40 double domainLength_,
41 double tau0_,
42 double C_param_,
43 bool forceTurb_,
44 int nVar_,
45 vector<double> &ScHips_,
46 bool performReaction_,
47 shared_ptr<void> vcantSol,
48 int seed,
49 int realization_):
50 nLevels(nLevels_),
51 domainLength(domainLength_),
52 tau0(tau0_),
53 C_param(C_param_),
54 forceTurb(forceTurb_),
55 ScHips(ScHips_),
56 nVar(nVar_),
57 rand(seed),
58 performReaction(performReaction_),
59 realization(realization_){
60
61 #ifdef REACTIONS_ENABLED
62 if(performReaction) {
63 shared_ptr<Cantera::Solution> cantSol = static_pointer_cast<Cantera::Solution>(vcantSol);
64 gas = cantSol->thermo();
65 nsp = gas->nSpecies();
66
67 bRxr = make_shared<batchReactor_cvode>(cantSol); // By default, use batchReactor_cvode
68
69 // Uncomment the following line to switch to batchReactor_cantera
70 // bRxr = make_unique<batchReactor_cantera>(cantSol);
71
72 if(forceTurb)
73 throw std::runtime_error("Error: forceTurb should be false if preformReaction is true");
74 }
75 #endif
76
77 // Resize vectors to the number of variables
78 varData.resize(nVar);
79 varName.resize(nVar);
80
81 set_tree(nLevels, domainLength, tau0);
82}
83
85
86hips::hips(double C_param_,
87 bool forceTurb_,
88 int nVar_,
89 vector<double> &ScHips_,
90 bool performReaction_,
91 shared_ptr<void> vcantSol,
92 int seed,
93 int realization_):
94 C_param(C_param_),
95 forceTurb(forceTurb_),
96 nVar(nVar_),
97 ScHips(ScHips_),
98 rand(seed),
99 performReaction(performReaction_),
100 realization(realization_){
101
102 #ifdef REACTIONS_ENABLED
103 // Initialize Cantera thermo phase and species count.
104 if(performReaction) {
105 shared_ptr<Cantera::Solution> cantSol = static_pointer_cast<Cantera::Solution>(vcantSol);
106 gas = cantSol->thermo();
107 nsp = gas->nSpecies();
108
109 // Set up the default batch reactor (cvode).
110 // bRxr = make_shared<batchReactor_cvode>(cantSol);
111
112 // Uncomment the following line to switch to batchReactor_cantera.
113 bRxr = make_shared<batchReactor_cantera>(cantSol);
114 }
115 #endif
116
117 // Resize vectors to accommodate the number of variables.
118 varData.resize(nVar);
119 varName.resize(nVar);
120}
121
123
124void hips::set_tree(int nLevels_, double domainLength_, double tau0_){
125
127 domainLength = domainLength_;
128 tau0 = tau0_;
129
130 if (nLevels == -1)
131 nLevels = nL;
132
133 iEta = nLevels - 3; // Kolmogorov level; if nLevels = 7, then 0, 1, 2, 3, (4), 5, 6; iEta=4 is the lowest swap level: swap grandchildren of iEta=4 at level 6.
134
135 int maxSc = 1.0;
136
137 for (int i=0; i<ScHips.size(); i++)
138 maxSc = ScHips[i]>maxSc ? ScHips[i] : maxSc;
139
140 if (maxSc > 1.0)
141 nLevels += ceil(log(maxSc)/log(4)); // Changing number of levels!
142
143 Nm1 = nLevels - 1;
144 Nm2 = nLevels - 2;
145 Nm3 = nLevels - 3;
146
147 // --------------------------
148
149 nparcels = static_cast<int>(pow(2, Nm1));
150 parcelTimes.resize(nparcels,0);
151 i_batchelor.resize(nVar,0);
152
153 vector<double> levelLengths(nLevels); // Including all levels, but last 2 don't count:
154 vector<double> levelTaus(nLevels); // Smallest scale is 2 levels up from bottom
155 levelRates = vector<double>(nLevels);
156
157 for (int i=0; i<nLevels; i++) {
158 levelLengths[i] = domainLength * pow(Afac,i);
159 //levelLengths[i] = domainLength * pow(Anew,i);
160
161 levelTaus[i] = tau0 * pow(levelLengths[i]/domainLength, 2.0/3.0) / C_param;
162 levelRates[i] = 1.0/levelTaus[i] * pow(2.0,i);
163 }
164
165 LScHips = ScHips.size() > 0 ? true : false;
166 if (LScHips) { // Ccorrect levels for high Sc (levels > Kolmogorov)
167 for (int i=iEta+1; i<nLevels; i++) {
168 levelTaus[i] = tau0 *
169 pow(levelLengths[iEta]/domainLength, 2.0/3.0) /
170 C_param;
171 levelRates[i] = 1.0/levelTaus[i] * pow(2.0,i);
172 }
173 }
174
175 //-------------------------------------------------
176
177 eddyRate_total = 0.0;
178 for (int i=0; i<=Nm3; i++)
180
181 eddyRate_inertial = 0.0;
182 for (int i=0; i<=iEta; i++)
184
185 //-------------------
186
187 i_plus.resize(nVar);
188
189 for (int k=0; k<nVar; k++) {
190 if (ScHips[k] < 1.0)
191 i_batchelor[k] = iEta + 1.5*log(ScHips[k])/log(4);
192 else if (ScHips[k] > 1.0)
193 i_batchelor[k] = iEta + log(ScHips[k])/log(4);
194 else
195 i_batchelor[k] = iEta;
196
197 i_plus[k] = ceil(i_batchelor[k]);
198 }
199
200 //------------------- Set the parcel addresses (index array)
201
202 varRho.resize(nparcels);
203 wPar.assign(nparcels, 1.0 / nparcels);
204
205 pLoc.resize(nparcels);
206 for (int i=0; i<nparcels; i++)
207 pLoc[i] = i;
208
209 currentIndex = 0.0;
210}
211
213
214void hips::set_tree(double Re_, double domainLength_, double tau0_, std::string ReApproach_) {
215 Re = Re_;
216 domainLength = domainLength_;
217 tau0 = tau0_;
218 ReApproach = ReApproach_;
219
220 double baseLevelEstimate = (3.0 / 4) * log(1 / Re) / log(Afac); // Calculate the base tree level estimate (non-integer)
221 int baseLevel;
222
223 if (ReApproach == "rounding") {
224 baseLevel = round(baseLevelEstimate); // Round the base level to the nearest integer
225 }
226 else if (ReApproach == "probability") {
227 baseLevel = ceil(baseLevelEstimate); // Ceil the base level to the nearest integer
228 int previousLevel = baseLevel - 1;
229 Prob = baseLevelEstimate - previousLevel; // Calculate the probability
230 }
231 else if (ReApproach == "micromixing") {
232 baseLevel = ceil(baseLevelEstimate); // Ceil the base level to the nearest integer
233 lStar = std::pow(Re, -3.0 / 4); // Calculate lStar based on Re
234 }
235 else if (ReApproach == "dynamic_A") {
236 baseLevel = round(baseLevelEstimate); // Round the base level to the nearest integer
237 Anew = exp(-log(Re) / ((4.0 / 3.0) * baseLevel)); // Calculate the new value of parameter A
238 }
239 else {
240 throw std::invalid_argument("Invalid ReApproach specified"); // Handle invalid approach case if needed
241 }
242
243 nL = baseLevel + 3; // Set the number of levels for the binary tree structure
244
245 //----------------------------------------------------------------------
246 nLevels = nL;
247 iEta = nLevels - 3; // Kolmogorov level
248
249 int maxSc = 1;
250 for (const auto &sc : ScHips)
251 maxSc = std::max(maxSc, static_cast<int>(sc));
252 if (maxSc > 1.0)
253 nLevels += ceil(log(maxSc) / log(4));
254
255 Nm1 = nLevels - 1;
256 Nm2 = nLevels - 2;
257 Nm3 = nLevels - 3;
258
259 nparcels = static_cast<int>(pow(2, Nm1));
260 parcelTimes.resize(nparcels, 0);
261 i_batchelor.resize(nVar, 0);
262
263 std::vector<double> levelLengths(nLevels); // Including all levels, but last 2 don't count
264 std::vector<double> levelTaus(nLevels); // Smallest scale is 2 levels up from bottom
265 levelRates.resize(nLevels);
266
267 for (int i = 0; i < nLevels; ++i) {
268 levelLengths[i] = domainLength * pow((ReApproach == "dynamic_A" ? Anew : Afac), i);
269
270 levelTaus[i] = tau0 * pow(levelLengths[i] / domainLength, 2.0 / 3.0) / C_param;
271 levelRates[i] = 1.0 / levelTaus[i] * pow(2.0, i);
272 }
273
274 if (ReApproach == "micromixing") { // Adjust rates for micromixing model
275 levelTaus[Nm3] = tau0 * pow(lStar / domainLength, 2.0 / 3.0) / C_param;
276 levelRates[Nm3] = 1.0 / levelTaus[Nm3] * pow(2.0, Nm3);
277 }
278
279 if (ReApproach == "probability") { // Adjust final mixing rate based on probability
280 levelRates[Nm3] = levelRates[nL - 3] * Prob;
281 }
282
283 LScHips = !ScHips.empty(); // Correct levels for high Sc (levels > Kolmogorov)
284 if (LScHips) {
285 for (int i = iEta + 1; i < nLevels; ++i) {
286 levelTaus[i] = tau0 * pow(levelLengths[iEta] / domainLength, 2.0 / 3.0) / C_param;
287 levelRates[i] = 1.0 / levelTaus[i] * pow(2.0, i);
288 }
289 }
290
291 //-----------------------------------------------------
292
293 eddyRate_total = 0.0;
294 for (int i = 0; i <= Nm3; ++i)
296
297 eddyRate_inertial = 0.0;
298 for (int i = 0; i <= iEta; ++i)
300
301 //-----------------------------------------------------
302
303 i_plus.resize(nVar);
304 for (int k = 0; k < nVar; ++k) {
305 if (ScHips[k] < 1.0)
306 i_batchelor[k] = iEta + 1.5 * log(ScHips[k]) / log(4);
307 else if (ScHips[k] > 1.0)
308 i_batchelor[k] = iEta + log(ScHips[k]) / log(4);
309 else
310 i_batchelor[k] = iEta;
311 i_plus[k] = ceil(i_batchelor[k]);
312 }
313
314 //-----------------------------------------------------
315
316 varRho.resize(nparcels);
317 wPar.assign(nparcels, 1.0 / nparcels);
318 pLoc.resize(nparcels);
319 for (int i = 0; i < nparcels; ++i)
320 pLoc[i] = i;
321
322 currentIndex = 0.0;
323}
324
342
343void hips::set_varData(std::vector<double> &v, std::vector<double> &w, const std::string &varN) {
344
345 varData[currentIndex] = std::make_shared<std::vector<double>>(projection(v, w));
346 varName[currentIndex] = varN;
347
348 currentIndex++;
349}
350
370
371void hips::set_varData(std::vector<double> &v, std::vector<double> &w, const std::string &varN, const std::vector<double> &rho) {
372
373 std::pair<std::vector<double>, std::vector<double>> results = projection(v, w, rho);
374
375 varData[currentIndex] = std::make_shared<std::vector<double>>(results.first);
376 varRho = results.second;
377 varName[currentIndex] = varN;
378
379 currentIndex++;
380}
381
403
404std::vector<double> hips::projection(std::vector<double> &vcfd, std::vector<double> &weight) {
405
406 xc = setGridCfd(weight); // Populate the physical domain for flow particles
407 xh = setGridHips(nparcels); // Populate the physical domain for hips parcels
408
409 int nc = xc.size() - 1;
410 int nh = xh.size() - 1;
411
412 std::vector<double> vh(nh, 0.0);
413 int jprev = 0;
414
415 for(int i = 0; i < nh; i++) {
416 for(int j = jprev + 1; j <= nc; ++j) {
417 if(xc[j] <= xh[i + 1]) {
418 double d1 = xc[j] - xc[j - 1];
419 double d2 = xc[j] - xh[i];
420 // calculation of shortest distance
421 // handling if distance is zero but substarction gives comutational error
422 double d = std::min(d1, d2) < 1E-15 ? 0 : std::min(d1, d2);
423
424 vh[i] += vcfd[j - 1] * d;
425 }
426 else {
427 double d1 = xh[i + 1] - xc[j - 1];
428 double d2 = xh[i + 1] - xh[i];
429 // calculation of shortest distance
430 // handling if distance is zero but substarction gives comutational error
431 double d = std::min(d1, d2) < 1E-15 ? 0 : std::min(d1, d2);
432
433 vh[i] += vcfd[j - 1] * d;
434 jprev = j - 1;
435 break;
436 }
437 }
438 vh[i] /= (xh[i + 1] - xh[i]);
439 }
440 return vh;
441}
442
487
488std::pair<std::vector<double>, std::vector<double>>
489hips::projection(std::vector<double> &vcfd,
490 std::vector<double> &weight,
491 const std::vector<double> &density) {
492 // Build CFD and HiPS grids
493 xc = setGridCfd(weight);
495
496 int nc = xc.size() - 1; // CFD cell count
497 int nh = xh.size() - 1; // HiPS parcel count
498
499 std::vector<double> vh(nh, 0.0); // parcel-averaged phi
500 std::vector<double> rho_h(nh, 0.0); // parcel-averaged density
501
502 int jprev = 0; // remember where we left off in CFD cells
503
504 for (int i = 0; i < nh; i++)
505 {
506 double M = 0.0; // total mass in this parcel
507 double Mphi = 0.0; // total (mass * phi) in this parcel
508
509 double parcel_length = xh[i + 1] - xh[i];
510
511 for (int j = jprev + 1; j <= nc; ++j)
512 {
513 // Find geometric overlap between CFD cell and HiPS parcel
514 double overlap_start = std::max(xh[i], xc[j - 1]);
515 double overlap_end = std::min(xh[i + 1], xc[j]);
516
517 double overlap_len = overlap_end - overlap_start;
518 if (overlap_len <= 0.0) continue;
519
520 // Effective length scaled by wPar[i]
521 double effective_len = overlap_len * (wPar[i] / parcel_length);
522
523 // Accumulate mass and mass*phi
524 double rho = density[j - 1];
525 double phi = vcfd[j - 1];
526 M += rho * effective_len;
527 Mphi += rho * phi * effective_len;
528
529 // If CFD cell ends after parcel end move to next parcel
530 if (xc[j] >= xh[i + 1]) {
531 jprev = j - 1;
532 break;
533 }
534 }
535
536 // Convert total mass average density and phi
537 if (wPar[i] > 0.0) rho_h[i] = M / wPar[i];
538 if (M > 0.0) vh[i] = Mphi / M;
539 }
540
541 return {vh, rho_h};
542}
543
562
563std::vector<double> hips::setGridCfd(std::vector<double> &w) {
564
565 double sumw = 0.0;
566 for(int i=0; i<w.size(); i++)
567 sumw += w[i];
568
569 std::vector<double> pos; // Initializing a vector to hold the grid positions
570 double posL = 0.0; // Initializing the starting position
571
572 int i = 0;
573
574 while (i <= w.size()) { // Generate the grid positions based on the weights
575 pos.push_back(posL); // Add the current position to the grid
576 posL += w[i]/sumw; // Move to the next position by adding the corresponding weight
577 i++;
578 }
579 return pos; // Return the generated grid positions
580}
581
600
601std::vector<double> hips::setGridHips(int N){
602
603 std::vector<double> xh(N + 1); // Initialize a vector to hold the grid points
604 double step = 1.0 / N; // Calculate the step size
605
606 for(int i = 0; i <= N; i++) // Populate the grid with evenly spaced points
607 xh[i] = i * step;
608
609 return xh; // Return the generated grid
610}
611
648
649void hips::calculateSolution(const double tRun, bool shouldWriteData) {
650
651 unsigned long long nEddies = 0; // Number of eddy events
652 int fileCounter = 0; // Number of data files written
653 int iLevel; // Tree level of EE with top at iLevel=0
654 int iTree; // One of two subtrees involved in swap at iLevel
655 time = 0.0; // Initialize simulation time
656 int lastEddyOutput = 0; // Track last eddy-based output event
657
658 // Apply default values if user hasn't set them
661 useEddyBasedWriting = true; // Default to eddy-based writing
662 }
663
664 sample_hips_eddy(dtEE, iLevel); // Get first EE at time 0+dtEE
665 nEddies++;
666 eddyCounter = 0; // Reset eddy counter at start
667 lastOutputTime = 0.0; // Reset last output time
668
669 while (time + dtEE <= tRun) {
670 time += dtEE;
671 selectAndSwapTwoSubtrees(iLevel, iTree);
672 advanceHips(iLevel, iTree); // Reaction and micromixing (if needed) to t=time
673
674 sample_hips_eddy(dtEE, iLevel);
675 nEddies++;
676 eddyCounter++;
677
678 // Only check the selected mode (set in example code or by default)
679 bool writeByEddy = (useEddyBasedWriting && eddyCounter >= lastEddyOutput + outputIntervalEddy);
680 bool writeByTime = (useTimeBasedWriting && time - lastOutputTime >= outputIntervalTime);
681
682 if (shouldWriteData) {
683 if (writeByEddy) { // Only write if using eddy-based writing
684 writeData(realization, ++fileCounter, time);
685 lastEddyOutput = eddyCounter; // Update last output event
686 }
687 else if (writeByTime) { // Only write if using time-based writing
688 writeData(realization, ++fileCounter, time);
690 }
691 }
692 }
693
694 // Ensure the final time step completes
695 time = tRun;
696 iLevel = 0;
697 iTree = 0;
698
699 if (performReaction)
700 reactParcels_LevelTree(iLevel, iTree); // React all parcels up to end time
702}
703
720
721void hips::sample_hips_eddy(double &dtEE, int &iLevel) {
722
723 static double c1 = 1.0 - pow(2.0, 5.0/3.0*(iEta+1));
724 static double c2 = pow(2.0, Nm2) - pow(2.0, iEta+1);
725 static double c3 = pow(2.0, iEta+1);
726
727 //--------------- time to next eddy
728
729 double r = rand.getRand();
730 dtEE = -log(r)/eddyRate_total;
731
732 //----------------- get eddy level
733
734 r = rand.getRand();
735
736 if ( r <= eddyRate_inertial/eddyRate_total) { // Inertial region
737 r = rand.getRand();
738 iLevel = ceil(3.0/5.0*log2(1.0-r*c1) - 1.0);
739 if (iLevel < 0) iLevel = 0;
740 if (iLevel > iEta) iLevel = iEta;
741 }
742
743 else { // "Batchelor" region
744 r = rand.getRand();
745 iLevel = ceil(log2(r*c2 + c3) - 1.0);
746 if (iLevel < iEta+1) iLevel = iEta+1;
747 if (iLevel > Nm3) iLevel = Nm3;
748 }
749 return;
750}
751
802
803void hips::selectAndSwapTwoSubtrees(const int iLevel, int &iTree) {
804
805 iTree = rand.getRandInt((1 << iLevel)-1);
806 int zero_q = rand.getRandInt(1); // 0q where q is 0 or 1
807 int one_r = 2 + rand.getRandInt(1); // 1r where r is 0 or 1
808
809 int Qstart = (zero_q << (Nm3-iLevel)) + (iTree << (Nm1-iLevel)); // starting index of Q parcels
810 int Rstart = (one_r << (Nm3-iLevel)) + (iTree << (Nm1-iLevel)); // starting index of R parcels
811 int nPswap = 1 << (Nm3-iLevel); // number of parcels that will be swapped
812
813 int Qend = Qstart + nPswap; // inclusive indices are Qstart to Qend-1
814 int Rend = Rstart + nPswap; // inclusive indices are Rstart to Rend-1
815 vector<int> aa(pLoc.begin()+Qstart, pLoc.begin()+Qend);
816 copy(pLoc.begin()+Rstart, pLoc.begin()+Rend, pLoc.begin()+Qstart); // python: pLoc[Qstart:Qend]=pLoc[Rstart:Rend]
817 copy(aa.begin(), aa.end(), pLoc.begin()+Rstart); // python: pLoc[Rstart:Rend]=aa
818}
819
844
845void hips::advanceHips(const int iLevel, const int iTree) {
846
847 if (forceTurb && iLevel == 0) {
848 forceProfile(); // Forcing for statistically stationary
849 }
850
851 bool rxnDone = false; // React all variables once
852 for (int k = 0; k < nVar; k++) { // Upon finding first variable needing micromixing
853 // Combined condition check with approach condition
854 if ((iLevel >= i_plus[k]) ||
855 (iLevel == i_plus[k] - 1 && rand.getRand() <= i_plus[k] - i_batchelor[k])) {
856 if (!rxnDone && performReaction) {
857 reactParcels_LevelTree(iLevel, iTree);
858 rxnDone = true;
859 }
860 mixAcrossLevelTree(k, iLevel, iTree);
861 }
862 }
863}
864
892
893int hips::getVariableIndex(const std::string &varName) const {
894
895 auto it = std::find(this->varName.begin(), this->varName.end(), varName);
896 if (it == this->varName.end()) {
897 throw std::runtime_error("Error: Variable name '" + varName + "' not found.");
898 }
899 return std::distance(this->varName.begin(), it);
900}
901
932
933void hips::reactParcels_LevelTree(const int iLevel, const int iTree) {
934
935 #ifdef REACTIONS_ENABLED
936 // ---- cache indices once
937 const int enthalpyIdx = getVariableIndex("enthalpy");
938 std::vector<int> yIdx(nsp);
939 for (int k = 0; k < nsp; ++k) {
940 yIdx[k] = getVariableIndex(gas->speciesName(k)); // map species name -> var index
941 }
942
943 const int nP = 1 << (Nm1 - iLevel);
944 const int istart = iTree * nP;
945 const int iend = istart + nP;
946
947 std::vector<double> y(nsp);
948
949 for (int i = istart; i < iend; ++i) {
950 const int ime = pLoc[i];
951 const double dt = time - parcelTimes[ime];
952
953 // Pull current state
954 double h = (*varData[enthalpyIdx])[ime];
955 for (int k = 0; k < nsp; ++k) {
956 y[k] = (*varData[yIdx[k]])[ime];
957 }
958
959 // ---- store old density BEFORE chemistry
960 const double rho_old = varRho[ime];
961
962 if (performReaction) {
963 // Advance chemistry; bRxr updates its state internally
964 bRxr->react(h, y, dt);
965
966 // Get new density from the reactor/EOS
967 const double rho_new = bRxr->getDensity();
968
969 // Keep per-parcel mass m = rho * wPar * V_tot constant
970 if (rho_new > 0.0) {
971 wPar[ime] *= (rho_old / rho_new);
972 varRho[ime] = rho_new;
973 }
974
975 }
976
977 // Write back enthalpy and species (post-reaction)
978 (*varData[enthalpyIdx])[ime] = h;
979 for (int k = 0; k < nsp; ++k) {
980 (*varData[yIdx[k]])[ime] = y[k];
981 }
982
983 parcelTimes[ime] = time;
984 }
985
986 #endif
987}
988
1028void hips::mixAcrossLevelTree(int kVar, const int iLevel, const int iTree) {
1029
1030 int istart;
1031 int iend;
1032
1033 int nPmix = 1 << (nLevels - iLevel - 2); // Number of parcels mixed together
1034 int ime;
1035
1036 //---------- Mix left branch of iTree ----------
1037 istart = iTree << (Nm1 - iLevel);
1038 iend = istart + nPmix;
1039
1040 double s = 0.0; // sum (value or value*mass)
1041 double msum = 0.0; // mass sum (only used if weighted)
1042
1043 for (int i = istart; i < iend; i++) {
1044 ime = pLoc[i];
1045 if (performReaction) {
1046 double m = varRho[ime] * wPar[ime];
1047 s += (*varData[kVar])[ime] * m;
1048 msum += m;
1049 } else {
1050 s += (*varData[kVar])[ime];
1051 }
1052 }
1053
1054 double avg = performReaction
1055 ? ((msum > 0.0) ? (s / msum) : 0.0)
1056 : (s / nPmix);
1057
1058 for (int i = istart; i < iend; i++) {
1059 ime = pLoc[i];
1060 (*varData[kVar])[ime] = avg;
1061 }
1062
1063 //---------- Mix right branch of iTree ----------
1064 istart = iend;
1065 iend = istart + nPmix;
1066
1067 s = 0.0;
1068 msum = 0.0;
1069
1070 for (int i = istart; i < iend; i++) {
1071 ime = pLoc[i];
1072 if (performReaction) {
1073 double m = varRho[ime] * wPar[ime];
1074 s += (*varData[kVar])[ime] * m;
1075 msum += m;
1076 } else {
1077 s += (*varData[kVar])[ime];
1078 }
1079 }
1080
1081 avg = performReaction
1082 ? ((msum > 0.0) ? (s / msum) : 0.0)
1083 : (s / nPmix);
1084
1085 for (int i = istart; i < iend; i++) {
1086 ime = pLoc[i];
1087 (*varData[kVar])[ime] = avg;
1088 }
1089}
1090
1113
1115 // Loop through each variable in the HiPS profile
1116 for (int k = 0; k < varData.size(); k++) {
1117 double s=0; // Temporary variable for summation
1118
1119 //---------- Force the left half of parcels to average 0 ----------
1120
1121 for (int i = 0; i < nparcels >> 1; i++)
1122 s += (*varData[k])[pLoc[i]]; // Calculate the sum of values in the left half of parcels
1123
1124 s /= (nparcels >> 1); // Calculate the average of values in the left half of parcels
1125
1126 for (int i = 0; i < nparcels >> 1; i++)
1127 (*varData[k])[pLoc[i]] += (-s - 0.0); // Adjust values in the left half of parcels to achieve an average of 0
1128
1129 //---------- Force the right half of parcels to average 1 ----------
1130 s = 0.0;
1131
1132 for (int i = nparcels >> 1; i < nparcels; i++)
1133 s += (*varData[k])[pLoc[i]]; // Calculate the sum of values in the right half of parcels
1134
1135 s /= (nparcels >> 1); // Calculate the average of values in the right half of parcels
1136
1137 for (int i = nparcels >> 1; i < nparcels; i++)
1138 (*varData[k])[pLoc[i]] += (-s + 1.0); // Adjust values in the right half of parcels to achieve an average of 1
1139 }
1140}
1141
1169
1170void hips::writeData(int real, const int ifile, const double outputTime) {
1171
1172 stringstream ss1, ss2;
1173 string s1, s2;
1174
1175 // Prepare directory path
1176 ss1 << "../data/rlz_" << setfill('0') << setw(5) << real;
1177 ss1 >> s1;
1178
1179 // Create directories if they don't exist
1180 #ifdef _WIN32
1181 system(("mkdir " + s1).c_str());
1182 #else
1183 system(("mkdir -p " + s1).c_str());
1184 #endif
1185
1186 ss2 << "Data_" << setfill('0') << setw(5) << ifile << ".dat";
1187 ss2 >> s2;
1188
1189 string fname = s1 + "/" + s2;
1190 ofstream ofile(fname.c_str());
1191 cout << endl << "writing data for time " << outputTime << " to file: " << fname.c_str();
1192
1193 // Check if file opened successfully
1194 if (!ofile) {
1195 cerr << "Error: Unable to open file " << fname << " for writing!" << endl;
1196 return;
1197 }
1198
1199 // Write metadata (header information)
1200 ofile << "# time = " << outputTime << "\n";
1201 ofile << "# Grid Points = " << nparcels << "\n";
1202
1203 // Write column names (include temperature if reactions are enabled)
1204 if(performReaction)
1205 ofile << setw(19) << "# Temp"; // Include temperature column if reactions are enabled
1206
1207 for (const auto& varN : varName) {
1208 ofile << setw(19) << "# " << varN;
1209 }
1210 ofile << endl; // End of the header line
1211
1212 // Set scientific notation and precision
1213 ofile << scientific;
1214 ofile << setprecision(10);
1215
1216 // Write data
1217 for (int i = 0; i < nparcels; i++) {
1218 // Write temperature first if reactions are enabled
1219 #ifdef REACTIONS_ENABLED
1220 if(performReaction) {
1221 vector<double> yy(nsp);
1222 for(int k=0; k<nsp; k++)
1223 yy[k] = (*varData[k+1])[pLoc[i]];
1224 gas->setMassFractions(yy.data());
1225 gas->setState_HP((*varData[0])[pLoc[i]], gas->pressure());
1226 ofile << setw(19) << gas->temperature();
1227 }
1228 #endif
1229
1230 // Write variables
1231 for (int k = 0; k < nVar; k++) {
1232 ofile << setw(19) << (*varData[k])[pLoc[i]];
1233 }
1234 ofile << endl;
1235 }
1236
1237 ofile.close();
1238 // cout << "Data successfully written to: " << fname << endl;
1239}
1240
1272
1273// cfd
1274// i= 0 1 2 3
1275// | * | * | * | * |
1276
1277// hips
1278// | * | * | * | * | * | * | * | * | * | * |
1279// j= 0 1 2 3 4 5 6 7 8 9
1280
1281std::vector<double> hips::projection_back(std::vector<double> &vh) {
1282
1283 int nh = xh.size() - 1;
1284 int nc = xc.size() - 1;
1285
1286 std::vector<double> vc(nc, 0.0);
1287 int jprev = 0;
1288
1289 for (int i = 0; i < nc; ++i) {
1290 for (int j = jprev + 1; j <= nh; ++j) {
1291 if (xh[j] <= xc[i + 1]) {
1292 double d1 = xh[j] - xh[j - 1];
1293 double d2 = xh[j] - xc[i];
1294 // calculation of shortest distance
1295 // handling if distance is zero but substarction gives comutational error
1296 double d = std::min(d1, d2) < 1E-15 ? 0 : std::min(d1, d2);
1297
1298 vc[i] += vh[j - 1] * d;
1299 } else {
1300 double d1 = xc[i + 1] - xh[j - 1];
1301 double d2 = xc[i + 1] - xc[i];
1302 // calculation of shortest distance
1303 // handling if distance is zero but substarction gives comutational error
1304 double d = std::min(d1, d2) < 1E-15 ? 0 : std::min(d1, d2);
1305
1306 vc[i] += vh[j - 1] * d;
1307
1308 jprev = j - 1;
1309 break;
1310 }
1311 }
1312 vc[i] /= (xc[i + 1] - xc[i]);
1313 }
1314 return vc;
1315}
1316
1354
1355std::vector<double> hips::projection_back_with_density(std::vector<double> &vh,
1356 std::vector<double> &rho_h,
1357 std::vector<double> &rho_c) {
1358 const int nh = static_cast<int>(xh.size()) - 1; // # HiPS parcels
1359 const int nc = static_cast<int>(xc.size()) - 1; // # CFD cells
1360
1361 std::vector<double> phi_c(nc, 0.0); // CFD-side variable (output)
1362 std::vector<double> M_cfd(nc, 0.0); // mass in each CFD cell
1363 std::vector<double> Mphi_cfd(nc, 0.0);// mass*phi in each CFD cell
1364
1365 int jprev = 0;
1366 for (int i = 0; i < nc; ++i) {
1367 // cell geometry (normalized length, since xc is 0..1)
1368 const double cell_len = xc[i + 1] - xc[i];
1369
1370 for (int j = jprev + 1; j <= nh; ++j) {
1371 // geometric overlap between parcel j-1 and CFD cell i
1372 const double overlap_start = std::max(xh[j - 1], xc[i]);
1373 const double overlap_end = std::min(xh[j], xc[i + 1]);
1374 const double overlap_len = overlap_end - overlap_start;
1375 if (overlap_len <= 0.0) continue;
1376
1377 // scale overlap by current parcel volume fraction wPar
1378 const double parcel_len = xh[j] - xh[j - 1]; // = 1.0/nh
1379 const double effective_len = overlap_len * (wPar[pLoc[j - 1]] / parcel_len);
1380
1381 // accumulate mass and mass*phi into CFD cell i
1382 const double rhoP = rho_h[j - 1];
1383 const double phiP = vh[j - 1];
1384 M_cfd[i] += rhoP * effective_len;
1385 Mphi_cfd[i] += rhoP * phiP * effective_len;
1386
1387 // advance parcel index if we reached end of this CFD cell
1388 if (xc[i + 1] <= xh[j]) { jprev = j - 1; break; }
1389 }
1390
1391 // recover CFD density and variable
1392 rho_c[i] = (cell_len > 0.0) ? (M_cfd[i] / cell_len) : 0.0;
1393 phi_c[i] = (M_cfd[i] > 1e-300) ? (Mphi_cfd[i] / M_cfd[i]) : 0.0;
1394 }
1395 return phi_c;
1396}
1397
1419
1420std::vector<std::vector<double>> hips::get_varData() {
1421 std::vector<std::vector<double>> varDataProjections;
1422
1423 for (int i = 0; i < varData.size(); i++) {
1424
1425 // Reorder using pLoc
1426 std::vector<double> vh(nparcels);
1427 for (int j = 0; j < nparcels; j++) {
1428 vh[j] = (*varData[i])[pLoc[j]];
1429 }
1430
1431 std::vector<double> vc = projection_back(vh);
1432 varDataProjections.push_back(vc);
1433 }
1434
1435 return varDataProjections;
1436}
1437
1458
1459std::pair<std::vector<std::vector<double>>, std::vector<double>> hips::get_varData_with_density() {
1460 std::vector<std::vector<double>> varDataProjections;
1461
1462 // Reorder varRho based on pLoc
1463 std::vector<double> rho_h(nparcels);
1464 for (int i = 0; i < nparcels; i++) {
1465 rho_h[i] = varRho[pLoc[i]];
1466 }
1467
1468 std::vector<double> rho_c = projection_back(rho_h);
1469
1470 for (int i = 0; i < varData.size(); i++) {
1471
1472 // Reorder variable data using pLoc
1473 std::vector<double> vh(nparcels);
1474 for (int j = 0; j < nparcels; j++) {
1475 vh[j] = (*varData[i])[pLoc[j]];
1476 }
1477
1478 auto vc = projection_back_with_density(vh, rho_h, rho_c);
1479 varDataProjections.push_back(vc);
1480 }
1481
1482 return {varDataProjections, rho_c};
1483}
1484
1497
1499
1500 outputIntervalEddy = interval;
1501 useEddyBasedWriting = true;
1502 useTimeBasedWriting = false;
1503}
1504
1517
1518void hips::setOutputIntervalTime(double interval) {
1519
1520 outputIntervalTime = interval;
1521 useTimeBasedWriting = true;
1522 useEddyBasedWriting = false;
1523}
1524
1538
1540
1541 std::string filepath = "../post/parameters.dat";
1542 std::ofstream file(filepath);
1543
1544 if (!file) {
1545 std::cerr << "Error: Could not open " << filepath << " for writing!\n";
1546 return;
1547 }
1548
1549 // Write user-defined input parameters
1550 file << "nLevels " << nLevels << "\n";
1551 file << "domainLength " << domainLength << "\n";
1552 file << "tau0 " << tau0 << "\n";
1553 file << "C_param " << C_param << "\n";
1554 file << "forceTurb " << forceTurb << "\n";
1555 file << "nVar " << nVar << "\n";
1556 file << "performReaction " << performReaction << "\n";
1557 file << "realization " << realization << "\n";
1558
1559 // Write variable names
1560 if (!varName.empty()) {
1561 file << "varName ";
1562 for (const std::string &name : varName) {
1563 file << name << " ";
1564 }
1565 file << "\n";
1566 } else {
1567 file << "varName (undefined)\n";
1568 }
1569
1570 // Write i_batchelor vector if it exists and has the correct size
1571 if (!i_batchelor.empty() && i_batchelor.size() == nVar) {
1572 file << "i_batchelor ";
1573 for (const auto &val : i_batchelor) {
1574 file << val << " ";
1575 }
1576 file << "\n";
1577 } else {
1578 file << "i_batchelor (undefined or size mismatch)\n";
1579 }
1580
1581 file.close();
1582 //cout << endl << "All parameters saved in: " << filepath << std::endl;
1583}
int Nm3
nLevels - 3
Definition hips.h:48
std::vector< double > setGridHips(int N)
Generates a physical domain for HiPS parcels.
Definition hips.cc:601
std::vector< double > parcelTimes
current times corresponding to the parcel states
Definition hips.h:71
double lastOutputTime
Last time data was written.
Definition hips.h:81
int Nm2
nLevels - 2
Definition hips.h:47
int nVar
number of parcel variables (e.g., h, ysp)
Definition hips.h:44
int nLevels_
number of tree levels?
Definition hips.h:43
int getVariableIndex(const std::string &varName) const
Retrieves the index of a variable by its name in the varName list.
Definition hips.cc:893
std::vector< double > xc
vector containing physical domain of flow particles
Definition hips.h:74
std::vector< int > pLoc
parcel index array for fast implementation of swaps
Definition hips.h:25
bool performReaction
flag indicating whether chemical reactions are performed in the simulation
Definition hips.h:54
void forceProfile()
Adjusts the HiPS profile to enforce statistical stationarity.
Definition hips.cc:1114
std::string ReApproach
Definition hips.h:77
std::vector< double > xh
vector containing physical domain of HiPS parcels
Definition hips.h:75
void set_tree(int nLevels_, double domainLength_, double tau0_)
Sets up the HiPS tree using explicitly specified tree parameters.
Definition hips.cc:124
std::vector< double > wPar
parcel volume fractions
Definition hips.h:26
int nL
adjusted number of levels based on the Reynolds number
Definition hips.h:50
bool forceTurb
forcing function for statistically stationary: -1 = none, 1 = source term, 2 = dir
Definition hips.h:51
std::vector< double > projection_back(std::vector< double > &vb)
Projects HiPS parcel values back onto the flow particles.
Definition hips.cc:1281
void setOutputIntervalEddy(int interval)
Sets the interval (in number of eddy events) for writing simulation data.
Definition hips.cc:1498
std::vector< std::vector< double > > get_varData()
Retrieves the final data from the simulation.
Definition hips.cc:1420
std::vector< int > i_plus
ceil(i_batchelor)
Definition hips.h:68
double outputIntervalTime
Default: write data every 0.1s.
Definition hips.h:79
double Afac
level lengthscale reduction factor (0.5)
Definition hips.h:59
double C_param
Eddy frequency parameter.
Definition hips.h:36
int nsp
number of species
Definition hips.h:45
int realization
number of realizations
Definition hips.h:22
const int DEFAULT_EDDY_INTERVAL
Default: Write every 1000 eddies.
Definition hips.h:84
void selectAndSwapTwoSubtrees(const int iLevel, int &iTree)
Performs eddy events by swapping parcels within the HiPS tree.
Definition hips.cc:803
void writeData(int real, const int ifile, const double outputTime)
Writes simulation data to a file for a specific realization, time, and file index.
Definition hips.cc:1170
double eddyRate_inertial
total rate of all eddies 0 through iEta (= eddyRate_total if Sc=1)
Definition hips.h:58
double Anew
adjusted level lengthscale reduction factor for dynamic adjustment of reduction factor
Definition hips.h:64
int currentIndex
member variable to keep track of current index of variables
Definition hips.h:41
std::vector< double > projection_back_with_density(std::vector< double > &vh, std::vector< double > &rho_h, std::vector< double > &rho_c)
Projects HiPS parcel values and densities back onto the flow particles (CFD cells).
Definition hips.cc:1355
std::vector< double > i_batchelor
Batchelor level for variable Sc scalars; NOTE: double, as in, between levels.
Definition hips.h:73
void calculateSolution(const double tRun, bool shouldWriteData=false)
Runs the HiPS simulation, advancing the solution using eddy events.
Definition hips.cc:649
randomGenerator rand
Definition hips.h:66
double time
current simulation time
Definition hips.h:56
void sample_hips_eddy(double &dt, int &iLevel)
Samples stochastic eddy events on the HiPS tree, determining the time increment and tree level.
Definition hips.cc:721
void setOutputIntervalTime(double interval)
Sets the interval (in simulation time) for writing simulation data.
Definition hips.cc:1518
std::vector< double > varRho
density
Definition hips.h:24
void reactParcels_LevelTree(const int iLevel, const int iTree)
Simulates chemical reactions for parcels affected by a micromixing event.
Definition hips.cc:933
hips(double C_param_, bool forceTurb_, int nVar_, std::vector< double > &ScHips_, bool performReaction, std::shared_ptr< void > vcantSol=nullptr, int seed=10, int realization_=1)
Constructor for initializing a HiPS object without building the tree immediately.
std::pair< std::vector< std::vector< double > >, std::vector< double > > get_varData_with_density()
Retrieves final simulation data, including both values and densities.
Definition hips.cc:1459
bool useTimeBasedWriting
Tracks if time writing is set.
Definition hips.h:83
std::vector< double > levelRates
list of eddy event rates at each level
Definition hips.h:72
std::vector< std::string > varName
vector containing the names of parcel variables
Definition hips.h:70
double Re
Reynolds number.
Definition hips.h:60
bool useEddyBasedWriting
Tracks if eddy writing is set.
Definition hips.h:82
void set_varData(std::vector< double > &v, std::vector< double > &w, const std::string &varN)
Assigns variables, their corresponding weights, and names to the parcels in the HiPS tree.
Definition hips.cc:343
int nparcels
number of parcels
Definition hips.h:40
double Prob
probability value for probability-based solution
Definition hips.h:62
void mixAcrossLevelTree(int kVar, const int iMixLevel, const int iTree)
Uniformly mixes parcels at a specified level and subtree within the HiPS model.
Definition hips.cc:1028
double dtEE
time increment to next eddy event
Definition hips.h:61
int iEta
Kolmogorov level (needed for variable Sc scalars)
Definition hips.h:49
double eddyRate_total
total rate of all eddies 0 through nLevels-3
Definition hips.h:57
double domainLength
length of domain (m)
Definition hips.h:34
int eddyCounter
Counter for eddy events.
Definition hips.h:80
std::vector< std::shared_ptr< std::vector< double > > > varData
vector of pointers to vector
Definition hips.h:23
std::vector< double > projection(std::vector< double > &vcfd, std::vector< double > &weight)
Projects values from flow particles onto HiPS parcels assuming constant density.
Definition hips.cc:404
std::vector< double > setGridCfd(std::vector< double > &w)
Generates a physical domain for flow particles based on their weights.
Definition hips.cc:563
void advanceHips(const int iLevel, const int iTree)
Advances the HiPS model by simulating micromixing and reactions at a specific tree level.
Definition hips.cc:845
double lStar
length of the level associated with the Reynolds number
Definition hips.h:63
int outputIntervalEddy
Default: write data every 10 eddy events.
Definition hips.h:78
double tau0
integral timescale
Definition hips.h:35
void saveAllParameters()
Saves all user-defined to a file.
Definition hips.cc:1539
int nLevels
number of tree levels
Definition hips.h:42
bool LScHips
hips schmidt number
Definition hips.h:53
int Nm1
nLevels - 1
Definition hips.h:46
std::vector< double > ScHips
vector containing Schmidt numbers related to each variable
Definition hips.h:69
int getRandInt(unsigned n)