chombo-discharge
Loading...
Searching...
No Matches
CD_ItoKMCStepperImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_ITOKMCSTEPPERIMPLEM_H
14#define CD_ITOKMCSTEPPERIMPLEM_H
15
16// Std includes
17#include <limits>
18
19// Chombo includes
20#include <ParmParse.H>
21#include <BinFab.H>
22#include <BoxIterator.H>
23
24// Our includes
26#include <CD_ItoKMCStepper.H>
28#include <CD_ParticleOps.H>
29#include <CD_ParticleLoops.H>
30#include <CD_DataOps.H>
31#include <CD_ParallelOps.H>
32#include <CD_Units.H>
33#include <CD_Timer.H>
34#include <CD_Location.H>
35#include <CD_NamespaceHeader.H>
36
37using namespace Physics::ItoKMC;
38
39namespace {
40// SoA per-cell extract/rebuild bridge for the ItoKMC reaction kernels. The reaction interface operates on
41// per-cell SoA scratch containers (one small ParticleSoA<P> per grid cell) extracted from the leaves; the
42// mutated scratches are concatenated back into the leaf. Mirrors ItoSolver::makeSuperparticles. The per-cell
43// vector is indexed by the box's Fortran cell offset (Box::index), which matches ParticleSoA::sortByCell.
44
45// Cell-sort a_leaf and extract each cell into a_cells[Box::index(iv)] (one ParticleSoA<P> per cell).
46template <typename P, typename Traits>
47inline void
48binLeafToCells(std::vector<ParticleSoA<P, Traits>>& a_cells,
50 const Box& a_box,
51 const Real a_dx,
52 const RealVect& a_probLo) noexcept
53{
54 a_leaf.sortByCell(a_box, a_dx * RealVect::Unit, a_probLo);
55
56 a_cells.clear();
57 a_cells.resize(a_box.numPts());
58 for (std::size_t c = 0; c < a_leaf.numCells(); c++) {
59 a_leaf.extractCell(c, a_cells[c]);
60 }
61}
62
63// Rebuild a_leaf from the per-cell scratch vector (concatenate all cells, then swap into the leaf).
64// The cells are written back in Fortran cell order, so the rebuilt leaf is cell-sorted by construction
65// and adopts the CSR mapping rather than letting the next sortByCell() rediscover it.
66template <typename P, typename Traits>
67inline void
68rebuildLeafFromCells(ParticleSoA<P, Traits>& a_leaf,
69 const std::vector<ParticleSoA<P, Traits>>& a_cells,
70 const Box& a_box,
71 const Real a_dx,
72 const RealVect& a_probLo) noexcept
73{
74 CH_assert(a_cells.size() == static_cast<std::size_t>(a_box.numPts()));
75
76 // Prefix-sum the per-cell counts into the CSR start offsets. This also gives the known total, so the
77 // append loop below allocates once instead of growing geometrically (O(log N) reallocations) on every
78 // reaction-kernel step.
79 std::vector<std::size_t> cellStart(a_cells.size() + 1, 0);
80 for (std::size_t c = 0; c < a_cells.size(); c++) {
81 cellStart[c + 1] = cellStart[c] + a_cells[c].size();
82 }
83
85 rebuilt.reserve(cellStart.back());
86
87 for (const ParticleSoA<P, Traits>& cell : a_cells) {
88 rebuilt.append(cell);
89 }
90
91 a_leaf.swap(rebuilt);
92 a_leaf.adoptCellSort(a_box, a_dx * RealVect::Unit, a_probLo, std::move(cellStart));
94
95} // namespace
96
97template <typename I, typename C, typename R, typename F>
100 CH_TIME("ItoKMCStepper::ItoKMCStepper");
101
102 m_verbosity = -1;
103 m_profile = false;
104 m_name = "ItoKMCStepper";
105 m_plasmaPhase = phase::gas;
106 m_dt = 0.0;
107 m_prevDt = -1.0;
108 m_maxGrowthDt = 1.E99;
109 m_maxShrinkDt = 1.E99;
110 m_time = 0.0;
111 m_timeStep = 0;
112 m_loadPerCell = 1.0;
113 m_redistributeCDR = true;
114 m_cdrProductInjection = CdrProductInjection::Particle;
115 m_fluidRealm = Realm::Primal;
116 m_particleRealm = Realm::Primal;
117 m_minParticleAdvectionCFL = 0.0;
118 m_maxParticleAdvectionCFL = 1.0;
119 m_minParticleDiffusionCFL = 0.0;
120 m_physicsDtFactor = 1.0;
121 m_maxParticleDiffusionCFL = std::numeric_limits<Real>::max();
122 m_minParticleAdvectionDiffusionCFL = std::numeric_limits<Real>::max();
123 m_maxParticleAdvectionDiffusionCFL = std::numeric_limits<Real>::max();
124 m_fluidAdvectionDiffusionCFL = 0.5;
125 m_relaxTimeFactor = std::numeric_limits<Real>::max();
126 m_minDt = std::numeric_limits<Real>::min();
127 m_maxDt = std::numeric_limits<Real>::max();
128 m_physicsDt = std::numeric_limits<Real>::max();
129 m_maxReducedField = 0.0;
130}
132template <typename I, typename C, typename R, typename F>
133ItoKMCStepper<I, C, R, F>::ItoKMCStepper(RefCountedPtr<ItoKMCPhysics>& a_physics) noexcept : ItoKMCStepper<I, C, R, F>()
134{
135 CH_TIME("ItoKMCStepper::ItoKMCStepper(RefCountrPtr<ItoKMCPhysics>)");
136
137 m_physics = a_physics;
138
139 if (m_physics->getNumPlasmaSpecies() == 0) {
140 MayDay::Abort("ItoKMCStepper::ItoKMCStepper -- numPlasmaSpecies = 0, there's no problem to solve here!");
141 }
142}
143
144template <typename I, typename C, typename R, typename F>
146{
147 CH_TIME("ItoKMCStepper::~ItoKMCStepper");
148}
149
150template <typename I, typename C, typename R, typename F>
151void
153{
154 CH_TIME("ItoKMCStepper::parseOptions");
155 if (m_verbosity > 5) {
156 pout() << m_name + "::parseOptions" << endl;
157 }
158
159 this->parseVerbosity();
160 this->parseExitOnFailure();
161 this->parseRedistributeCDR();
162 this->parseCdrProducts();
163 this->parsePlotVariables();
164 this->parseSuperParticles();
165 this->parseDualGrid();
166 this->parseLoadBalance();
167 this->parseTimeStepRestrictions();
168 this->parseParametersEB();
169}
170
171template <typename I, typename C, typename R, typename F>
172void
174{
175 CH_TIME("ItoKMCStepper::parseRuntimeOptions");
176 if (m_verbosity > 5) {
177 pout() << m_name + "::parseRuntimeOptions" << endl;
178 }
179
180 this->parseVerbosity();
181 this->parseExitOnFailure();
182 this->parseRedistributeCDR();
183 this->parseCdrProducts();
184 this->parsePlotVariables();
185 this->parseSuperParticles();
186 this->parseLoadBalance();
187 this->parseTimeStepRestrictions();
188 this->parseParametersEB();
189
190 m_ito->parseRuntimeOptions();
191 m_cdr->parseRuntimeOptions();
192 m_fieldSolver->parseRuntimeOptions();
193 m_rte->parseRuntimeOptions();
194 m_sigmaSolver->parseRuntimeOptions();
195
196 m_physics->parseRuntimeOptions();
197}
198
199template <typename I, typename C, typename R, typename F>
200void
202{
203 CH_TIME("ItoKMCStepper::parseVerbosity");
204 if (m_verbosity > 5) {
205 pout() << m_name + "::parseVerbosity" << endl;
206 }
207
208 ParmParse pp(m_name.c_str());
209
210 pp.get("verbosity", m_verbosity);
211 pp.get("profile", m_profile);
212}
213
214template <typename I, typename C, typename R, typename F>
215void
217{
218 CH_TIME("ItoKMCStepper::parseExitOnFailure");
219 if (m_verbosity > 5) {
220 pout() << m_name + "::parseExitOnFailure" << endl;
221 }
222
223 ParmParse pp(m_name.c_str());
224
225 pp.get("abort_on_failure", m_abortOnFailure);
226}
227
228template <typename I, typename C, typename R, typename F>
229void
231{
232 CH_TIME("ItoKMCStepper::parseRedistributeCDR");
233 if (m_verbosity > 5) {
234 pout() << m_name + "::parseRedistributeCDR" << endl;
235 }
236
237 ParmParse pp(m_name.c_str());
239 pp.get("redistribute_cdr", m_redistributeCDR);
240}
241
242template <typename I, typename C, typename R, typename F>
243void
245{
246 CH_TIME("ItoKMCStepper::parseCdrProducts");
247 if (m_verbosity > 5) {
248 pout() << m_name + "::parseCdrProducts" << endl;
249 }
251 ParmParse pp(m_name.c_str());
252
253 std::string str;
254
255 pp.get("cdr_products", str);
257 if (str == "mesh") {
258 m_cdrProductInjection = CdrProductInjection::Mesh;
259 }
260 else if (str == "particle") {
261 m_cdrProductInjection = CdrProductInjection::Particle;
263 else {
264 MayDay::Error(("ItoKMCStepper::parseCdrProducts - unknown '" + m_name + ".cdr_products = " + str +
265 "', must be 'mesh' or 'particle'")
266 .c_str());
267 }
268}
269
270template <typename I, typename C, typename R, typename F>
271void
273{
274 CH_TIME("ItoKMCStepper::parsePlotVariables");
275 if (m_verbosity > 5) {
276 pout() << m_name + "::parsePlotVariables" << endl;
277 }
278
279 m_plotConductivity = false;
280 m_plotCurrentDensity = false;
281 m_plotParticlesPerPatch = false;
282
283 // Read in plot variables.
284 ParmParse pp(m_name.c_str());
285 const int num = pp.countval("plt_vars");
286
287 if (num > 0) {
288 Vector<std::string> str(num);
289 pp.getarr("plt_vars", str, 0, num);
290
291 // Set plot variables
292 for (int i = 0; i < num; i++) {
293 if (str[i] == "conductivity") {
294 m_plotConductivity = true;
295 }
296 else if (str[i] == "current_density") {
297 m_plotCurrentDensity = true;
298 }
299 else if (str[i] == "particles_per_patch") {
300 m_plotParticlesPerPatch = true;
301 }
302 }
303 }
304}
305
306template <typename I, typename C, typename R, typename F>
307void
309{
310 CH_TIME("ItoKMCStepper::parseSuperParticles");
311 if (m_verbosity > 5) {
312 pout() << m_name + "::parseSuperParticles" << endl;
313 }
314
315 ParmParse pp(m_name.c_str());
316
317 // Cadence only. What to merge to, and whether to merge at a regrid, are properties of the solver and
318 // live there (ItoSolver.particles_per_cell / ItoSolver.regrid_superparticles). This is a property of
319 // the advance, which the solver has no notion of, so it stays here.
320 pp.get("merge_interval", m_mergeInterval);
321}
322
323template <typename I, typename C, typename R, typename F>
324void
326{
327 CH_TIME("ItoKMCStepper::parseDualGrid");
328 if (m_verbosity > 5) {
329 pout() << m_name + "::parseDualGrid" << endl;
330 }
332 ParmParse pp(m_name.c_str());
333
334 pp.get("dual_grid", m_dualGrid);
335
336 if (m_dualGrid) {
337 m_particleRealm = "ParticleRealm";
338
339 CH_assert(m_particleRealm != m_fluidRealm);
340 }
341 else {
342 m_particleRealm = m_fluidRealm;
343 }
344}
345
346template <typename I, typename C, typename R, typename F>
347void
349{
350 CH_TIME("ItoKMCStepper::parseLoadBalance");
351 if (m_verbosity > 5) {
352 pout() << m_name + "::parseLoadBalance" << endl;
353 }
354
355 ParmParse pp(m_name.c_str());
356
357 std::string str;
358
359 pp.get("load_balance_particles", m_loadBalanceParticles);
360 pp.get("load_balance_fluid", m_loadBalanceFluid);
361 pp.get("load_per_cell", m_loadPerCell);
362
363 // Box sorting for load balancing
364 pp.get("box_sorting", str);
365 if (str == "none") {
366 m_boxSort = BoxSorting::None;
367 }
368 else if (str == "std") {
369 m_boxSort = BoxSorting::Std;
370 }
371 else if (str == "shuffle") {
372 m_boxSort = BoxSorting::Shuffle;
373 }
374 else if (str == "morton") {
375 m_boxSort = BoxSorting::Morton;
376 }
377 else if (str == "hilbert") {
378 m_boxSort = BoxSorting::Hilbert;
379 }
380 else {
381 const std::string err = "ItoKMCStepper::parseLoadBalance - 'box_sorting = " + str + "' not recognized";
382
383 MayDay::Error(err.c_str());
384 }
385
386 // Get the load balancing index.
387 const int numIndices = pp.countval("load_indices");
388
389 if (numIndices > 0) {
390 pp.getarr("load_indices", m_loadBalanceIndices, 0, numIndices);
391 }
392 else {
393 const std::string err = "ItoKMCStepper::parseLoadBalance - 'load_indices' argument has zero entries";
394
395 MayDay::Error(err.c_str());
396 }
397}
398
399template <typename I, typename C, typename R, typename F>
400void
402{
403 CH_TIME("ItoKMCStepper::parseTimeStepRestrictions");
404 if (m_verbosity > 5) {
405 pout() << m_name + "::parseTimeStepRestrictions" << endl;
406 }
407
408 ParmParse pp(m_name.c_str());
409
410 pp.get("min_particle_advection_cfl", m_minParticleAdvectionCFL);
411 pp.get("max_particle_advection_cfl", m_maxParticleAdvectionCFL);
412 pp.get("min_particle_diffusion_cfl", m_minParticleDiffusionCFL);
413 pp.get("max_particle_diffusion_cfl", m_maxParticleDiffusionCFL);
414 pp.get("min_particle_advection_diffusion_cfl", m_minParticleAdvectionDiffusionCFL);
415 pp.get("max_particle_advection_diffusion_cfl", m_maxParticleAdvectionDiffusionCFL);
416 pp.get("fluid_advection_diffusion_cfl", m_fluidAdvectionDiffusionCFL);
417 pp.get("relax_dt_factor", m_relaxTimeFactor);
418 pp.get("min_dt", m_minDt);
419 pp.get("max_dt", m_maxDt);
420 pp.get("max_growth_dt", m_maxGrowthDt);
421 pp.get("max_shrink_dt", m_maxShrinkDt);
422 pp.get("physics_dt_factor", m_physicsDtFactor);
423
424 if (m_maxGrowthDt <= 1.0) {
425 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have max_growth_dt > 1.0");
426 }
427
428 if (m_maxShrinkDt <= 1.0) {
429 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have max_shrink_dt > 1.0");
430 }
431
432 if (m_relaxTimeFactor <= 0.0) {
433 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have relax_dt > 0.0");
434 }
435
436 if (m_minDt < 0.0) {
437 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have min_dt >= 0.0");
438 }
439
440 if (m_maxDt < 0.0) {
441 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have max_dt >= 0.0");
442 }
443
444 if (m_maxParticleAdvectionCFL <= 0.0) {
445 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have max_particle_advection_cfl > 0.0");
446 }
447
448 if (m_minParticleAdvectionCFL < 0.0) {
449 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have min_particle_advection_cfl >= 0.0");
450 }
451
452 if (m_maxParticleDiffusionCFL <= 0.0) {
453 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have particle_diffusion_cfl > 0.0");
454 }
455
456 if (m_minParticleDiffusionCFL < 0.0) {
457 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have particle_diffusion_cfl >= 0.0");
458 }
459
460 if (m_maxParticleAdvectionDiffusionCFL <= 0.0) {
461 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have particle_advection_diffusion_cfl > 0.0");
462 }
463
464 if (m_minParticleAdvectionDiffusionCFL < 0.0) {
465 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have particle_advection_diffusion_cfl >= 0.0");
466 }
467
468 if (m_fluidAdvectionDiffusionCFL <= 0.0) {
469 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have fluid_advection_diffusion_cfl > 0.0");
470 }
471
472 if (m_physicsDtFactor <= 0.0) {
473 MayDay::Error("ItoKMCStepper::parseTimeStepRestrictions() - must have physics_dft_factor > 0.0");
474 }
475}
476
477template <typename I, typename C, typename R, typename F>
478void
480{
481 CH_TIME("ItoKMCStepper::parseTimeStepRestrictions");
482 if (m_verbosity > 5) {
483 pout() << m_name + "::parseTimeStepRestrictions" << endl;
484 }
485
486 ParmParse pp(m_name.c_str());
487
488 std::string str;
489
490 pp.get("eb_tolerance", m_toleranceEB);
491}
492
493template <typename I, typename C, typename R, typename F>
494void
496{
497 CH_TIME("ItoKMCStepper::setupSolver");
498 if (m_verbosity > 5) {
499 pout() << m_name + "::setupSolvers" << endl;
500 }
501
502 this->setupIto();
503 this->setupCdr();
504 this->setupPoisson();
505 this->setupRadiativeTransfer();
506 this->setupSigma();
507}
508
509template <typename I, typename C, typename R, typename F>
510void
512{
513 CH_TIME("ItoKMCStepper::setupIto");
514 if (m_verbosity > 5) {
515 pout() << m_name + "::setupIto" << endl;
516 }
517
519 m_ito = factory.newLayout(m_physics->getItoSpecies());
520
521 m_ito->parseOptions();
522 m_ito->setAmr(m_amr);
523 m_ito->setPhase(m_plasmaPhase);
524 m_ito->setComputationalGeometry(m_computationalGeometry);
525 m_ito->setRealm(m_particleRealm);
526}
527
528template <typename I, typename C, typename R, typename F>
529void
531{
532 CH_TIME("ItoKMCStepper::setupCdr");
533 if (m_verbosity > 5) {
534 pout() << m_name + "::setupCdr" << endl;
535 }
536
538 m_cdr = factory.newLayout(m_physics->getCdrSpecies());
539
540 m_cdr->parseOptions();
541 m_cdr->setAmr(m_amr);
542 m_cdr->setPhase(m_plasmaPhase);
543 m_cdr->setComputationalGeometry(m_computationalGeometry);
544 m_cdr->setRealm(m_fluidRealm);
545}
546
547template <typename I, typename C, typename R, typename F>
548void
550{
551 CH_TIME("ItoKMCStepper::setupRadiativeTransfer");
552 if (m_verbosity > 5) {
553 pout() << m_name + "::setupRadiativeTransfer" << endl;
554 }
555
556 RtFactory<McPhoto, R> factory;
557 m_rte = factory.newLayout(m_physics->getRtSpecies());
558
559 m_rte->parseOptions();
560 m_rte->setPhase(m_plasmaPhase);
561 m_rte->setAmr(m_amr);
562 m_rte->setComputationalGeometry(m_computationalGeometry);
563 m_rte->setRealm(m_particleRealm);
564 m_rte->sanityCheck();
565}
566
567template <typename I, typename C, typename R, typename F>
568void
570{
571 CH_TIME("ItoKMCStepper::setupPoisson");
572 if (m_verbosity > 5) {
573 pout() << m_name + "::setupPoisson" << endl;
574 }
575
576 m_fieldSolver = RefCountedPtr<FieldSolver>(new F());
577 m_fieldSolver->parseOptions();
578 m_fieldSolver->setAmr(m_amr);
579 m_fieldSolver->setComputationalGeometry(m_computationalGeometry);
580 m_fieldSolver->setVoltage(m_voltage);
581 m_fieldSolver->setRealm(m_fluidRealm);
582}
583
584template <typename I, typename C, typename R, typename F>
585void
587{
588 CH_TIME("ItoKMCStepper::setupSigma");
589 if (m_verbosity > 5) {
590 pout() << m_name + "::setupSigma" << endl;
591 }
592
593 m_sigmaSolver = RefCountedPtr<SurfaceODESolver<1>>(new SurfaceODESolver<1>(m_amr));
594 m_sigmaSolver->parseOptions();
595 m_sigmaSolver->setRealm(m_fluidRealm);
596 m_sigmaSolver->setPhase(m_plasmaPhase);
597 m_sigmaSolver->setName("Surface charge");
598 m_sigmaSolver->setTime(0, 0.0, 0.0);
599}
600
601template <typename I, typename C, typename R, typename F>
602void
604{
605 CH_TIME("ItoKMCStepper::allocate");
606 if (m_verbosity > 5) {
607 pout() << m_name + "::allocate" << endl;
608 }
609
610 m_ito->allocate();
611 m_cdr->allocate();
612 m_rte->allocate();
613 m_fieldSolver->allocate();
614 m_sigmaSolver->allocate();
615
616 this->allocateInternals();
617}
618
619template <typename I, typename C, typename R, typename F>
620void
622{
623 CH_TIME("ItoKMCStepper::allocateInternals");
624 if (m_verbosity > 5) {
625 pout() << m_name + "::allocateInternals" << endl;
626 }
627
628 const int numItoSpecies = m_physics->getNumItoSpecies();
629 const int numCdrSpecies = m_physics->getNumCdrSpecies();
630 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
631 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
632
633 CH_assert(numPlasmaSpecies > 0);
634
635 // Scratch data.
636 m_amr->allocate(m_fluidScratch1, m_fluidRealm, m_plasmaPhase, 1);
637 m_amr->allocate(m_fluidScratchD, m_fluidRealm, m_plasmaPhase, SpaceDim);
638 m_amr->allocate(m_fluidScratchEB, m_fluidRealm, m_plasmaPhase, 1);
639
640 m_amr->allocate(m_particleScratch1, m_particleRealm, m_plasmaPhase, 1);
641 m_amr->allocate(m_particleScratchD, m_particleRealm, m_plasmaPhase, SpaceDim);
642 m_amr->allocate(m_particleScratchEB, m_particleRealm, m_plasmaPhase, 1);
643
644 // Storage for neutral density
645 m_amr->allocate(m_neutralDensity, m_fluidRealm, m_plasmaPhase, 1);
646
647 // Storage for conductivities on cell, cell faces, and EB faces.
648 m_amr->allocate(m_conductivityCell, m_fluidRealm, m_plasmaPhase, 1);
649 m_amr->allocate(m_conductivityFace, m_fluidRealm, m_plasmaPhase, 1);
650 m_amr->allocate(m_conductivityEB, m_fluidRealm, m_plasmaPhase, 1);
651
652 // Electric field data on both realms.
653 m_amr->allocate(m_electricFieldParticle, m_particleRealm, m_plasmaPhase, SpaceDim);
654 m_amr->allocate(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase, SpaceDim);
655
656 // Data for CDR-based solver mobilities
657 m_cdrMobilities.resize(numCdrSpecies);
658 m_cdrProducts.resize(numCdrSpecies);
659 for (int i = 0; i < numCdrSpecies; i++) {
660 m_amr->allocate(m_cdrMobilities[i], m_fluidRealm, m_plasmaPhase, 1);
661
662 m_cdrProducts[i] = RefCountedPtr<ParticleContainer<NoPayload>>(new ParticleContainer<NoPayload>());
663 m_amr->allocate(*m_cdrProducts[i], m_particleRealm);
664 }
665
666 // Fluid-realm scratch shared by computeMobilities() and computeDiffusionCoefficients().
667 m_fluidScratchIto.resize(numItoSpecies);
668 for (int i = 0; i < numItoSpecies; i++) {
669 m_amr->allocate(m_fluidScratchIto[i], m_fluidRealm, m_plasmaPhase, 1);
670 }
671
672 // Storage for the density gradients
673 m_fluidGradPhiIto.resize(numItoSpecies);
674 m_fluidPhiIto.resize(numItoSpecies);
675 m_fluidGradPhiCDR.resize(numCdrSpecies);
676 for (int i = 0; i < numItoSpecies; i++) {
677 m_amr->allocate(m_fluidGradPhiIto[i], m_fluidRealm, m_plasmaPhase, SpaceDim);
678 m_amr->allocate(m_fluidPhiIto[i], m_fluidRealm, m_plasmaPhase, 1);
679 }
680 for (int i = 0; i < numCdrSpecies; i++) {
681 m_amr->allocate(m_fluidGradPhiCDR[i], m_fluidRealm, m_plasmaPhase, SpaceDim);
682 }
683
684 // Storage for secondary particle and photon emission
685 m_secondaryParticles.resize(numItoSpecies);
686 m_secondaryPhotons.resize(numPhotonSpecies);
687
688 m_cdrFluxes.resize(numCdrSpecies);
689 m_cdrFluxesExtrap.resize(numCdrSpecies);
690
691 for (int i = 0; i < numItoSpecies; i++) {
692 m_secondaryParticles[i] = RefCountedPtr<ParticleContainer<ItoParticle>>(new ParticleContainer<ItoParticle>());
693 m_amr->allocate(*m_secondaryParticles[i], m_particleRealm);
694 }
695
696 for (int i = 0; i < numPhotonSpecies; i++) {
697 m_secondaryPhotons[i] = RefCountedPtr<ParticleContainer<Photon>>(new ParticleContainer<Photon>());
698 m_amr->allocate(*m_secondaryPhotons[i], m_particleRealm);
699 }
700
701 for (int i = 0; i < numCdrSpecies; i++) {
702 m_amr->allocate(m_cdrFluxes[i], m_particleRealm, m_plasmaPhase, 1);
703 m_amr->allocate(m_cdrFluxesExtrap[i], m_particleRealm, m_plasmaPhase, 1);
704 }
705
706 // Current density.
707 m_amr->allocate(m_currentDensity, m_fluidRealm, m_plasmaPhase, SpaceDim);
708
709 // Storage for the physics time step
710 m_amr->allocate(m_kmcDt, m_fluidRealm, m_plasmaPhase, 1);
711
712 // Storage required for the reaction network.
713 m_amr->allocate(m_fluidPPC, m_fluidRealm, m_plasmaPhase, numPlasmaSpecies);
714
715 if (numItoSpecies > 0) {
716 m_amr->allocate(m_particleItoPPC, m_particleRealm, m_plasmaPhase, numItoSpecies);
717 m_amr->allocate(m_particleOldItoPPC, m_particleRealm, m_plasmaPhase, numItoSpecies);
718 }
719 else {
720 // Allocate some dummy data -- makes it easier. Trust me.
721 m_amr->allocate(m_particleItoPPC, m_particleRealm, m_plasmaPhase, 1);
722 m_amr->allocate(m_particleOldItoPPC, m_particleRealm, m_plasmaPhase, 1);
723 }
724
725 if (numCdrSpecies > 0) {
726 m_amr->allocate(m_fluidCdrPPC, m_fluidRealm, m_plasmaPhase, numCdrSpecies);
727 m_amr->allocate(m_fluidOldCdrPPC, m_fluidRealm, m_plasmaPhase, numCdrSpecies);
728 m_amr->allocate(m_particleCdrProduction, m_particleRealm, m_plasmaPhase, numCdrSpecies);
729 }
730 else {
731 m_amr->allocatePointer(m_fluidCdrPPC, m_fluidRealm);
732 m_amr->allocatePointer(m_fluidOldCdrPPC, m_fluidRealm);
733
734 // Dummy data, for the same reason as the Ito holders above: the reconciliation kernels take it by reference.
735 m_amr->allocate(m_particleCdrProduction, m_particleRealm, m_plasmaPhase, 1);
736 }
737
738 if (numPhotonSpecies > 0) {
739 m_amr->allocate(m_particleYPC, m_particleRealm, m_plasmaPhase, numPhotonSpecies);
740 m_amr->allocate(m_fluidYPC, m_fluidRealm, m_plasmaPhase, numPhotonSpecies);
741 }
742 else {
743 // Allocate some dummy data -- makes it easier. Trust me.
744 m_amr->allocate(m_particleYPC, m_particleRealm, m_plasmaPhase, 1);
745 m_amr->allocate(m_fluidYPC, m_fluidRealm, m_plasmaPhase, 1);
746 }
747
748 DataOps::setValue(m_kmcDt, std::numeric_limits<Real>::max());
749}
750
751template <typename I, typename C, typename R, typename F>
752void
754{
755 CH_TIME("ItoKMCStepper::postInitialize");
756 if (m_verbosity > 5) {
757 pout() << m_name + "::postInitialize" << endl;
758 }
759}
760
761template <typename I, typename C, typename R, typename F>
762void
764{
765 CH_TIME("ItoKMCStepper::initialData");
766 if (m_verbosity > 5) {
767 pout() << m_name + "::initialData" << endl;
768 }
769
770 CH_assert(!(m_cdr.isNull()));
771 CH_assert(!(m_ito.isNull()));
772 CH_assert(!(m_rte.isNull()));
773 CH_assert(!(m_sigmaSolver.isNull()));
774 CH_assert(!(m_fieldSolver.isNull()));
775
776 m_ito->initialData();
777 m_cdr->initialData();
778 m_rte->initialData();
779 this->initialSigma();
780
781 // Make superparticles (makeSuperparticles() cell-sorts internally as needed and returns the containers
782 // patch-organized).
783 m_ito->makeSuperparticles(ItoSolver::WhichContainer::Bulk);
784
785 // Solve Poisson equation and compute the E-field
786 m_fieldSolver->setPermittivities();
787 this->computeSpaceChargeDensity();
788 this->solvePoisson();
789
790 // Fill solvers with velocities and diffusion coefficients
791 this->computeDriftVelocities();
792 this->computeDiffusionCoefficients();
793
794 // Fill the internal neutral density
795 this->fillNeutralDensity();
796}
797
798template <typename I, typename C, typename R, typename F>
799void
801{
802 CH_TIME("ItoKMCStepper::initialSigma");
803 if (m_verbosity > 5) {
804 pout() << m_name + "::initialSigma" << endl;
805 }
806
807 const RealVect probLo = m_amr->getProbLo();
808
809 EBAMRIVData& sigma = m_sigmaSolver->getPhi();
810
811 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
812 const DisjointBoxLayout& dbl = m_amr->getGrids(m_sigmaSolver->getRealm())[lvl];
813 const DataIterator& dit = dbl.dataIterator();
814 const EBISLayout& ebisl = m_amr->getEBISLayout(m_sigmaSolver->getRealm(), m_sigmaSolver->getPhase())[lvl];
815 const Real dx = m_amr->getDx()[lvl];
816
817 const int nbox = dit.size();
818
819#pragma omp parallel for schedule(runtime)
820 for (int mybox = 0; mybox < nbox; mybox++) {
821 const DataIndex& din = dit[mybox];
822
823 BaseIVFAB<Real>& phi = (*sigma[lvl])[din];
824 const EBISBox& ebisbox = ebisl[din];
825
826 CH_assert(phi.nComp() == 1);
827
828 auto kernel = [&](const VolIndex& vof) -> void {
829 const RealVect pos = probLo + Location::position(Location::Cell::Boundary, vof, ebisbox, dx);
830
831 phi(vof, 0) = m_physics->initialSigma(m_time, pos);
832 };
833
834 VoFIterator& vofit = (*m_amr->getVofIterator(m_sigmaSolver->getRealm(), m_sigmaSolver->getPhase())[lvl])[din];
835
836 BoxLoops::loop(vofit, kernel);
837 }
838 }
839
840 // Coarsen throughout the AMR hierarchy.
841 m_amr->conservativeAverage(sigma, m_fluidRealm, m_sigmaSolver->getPhase());
842
843 // Set surface charge to zero on electrode cut-cells.
844 m_sigmaSolver->resetElectrodes(sigma, 0.0);
845}
846
847template <typename I, typename C, typename R, typename F>
848void
850{
851 CH_TIME("ItoKMCStepper::postCheckpointSetup");
852 if (m_verbosity > 5) {
853 pout() << m_name + "::postCheckpointSetup" << endl;
854 }
856 m_ito->remap();
857
858 // Recompute the electric field.
859 this->postCheckpointPoisson();
860
861 // Compute velocities and diffusion coefficients so we're prepared for the next time step.
862 this->computeDriftVelocities();
863 this->computeDiffusionCoefficients();
864}
865
866template <typename I, typename C, typename R, typename F>
867void
869{
870 CH_TIME("ItoKMCStepper::postCheckpointPoisson");
871 if (m_verbosity > 5) {
872 pout() << m_name + "::postCheckpointPoisson" << endl;
874
875 // Do some post checkpointing stuff.
876 m_fieldSolver->postCheckpoint();
877
878 // Update ghost cells and re-compute the electric field from the HDF5 data.
879 MFAMRCellData& potential = m_fieldSolver->getPotential();
880
881 m_amr->conservativeAverage(potential, m_fluidRealm);
882 m_amr->interpGhostMG(potential, m_fluidRealm);
883
884 m_fieldSolver->computeElectricField();
886 // Fetch the electric field data on the plasma phase.
887 const EBAMRCellData E = m_amr->alias(m_plasmaPhase, m_fieldSolver->getElectricField());
888
889 // Copy onto the storage holding the electric field on the fluid realm. Then interpolate to centroids.
890 m_amr->copyData(m_electricFieldFluid, E);
891 m_amr->conservativeAverage(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
892 m_amr->interpGhostPwl(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
893 m_amr->interpToCentroids(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
894
895 // Copy onto the storage holding the electric field on the particle realm.
896 m_amr->copyData(m_electricFieldParticle, E);
897 m_amr->conservativeAverage(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
898 m_amr->interpGhostPwl(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
899 m_amr->interpToCentroids(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
900
901 // Set up the Poisson solver
902 m_fieldSolver->setupSolver();
903}
904
905#ifdef CH_USE_HDF5
906template <typename I, typename C, typename R, typename F>
907void
908ItoKMCStepper<I, C, R, F>::writeCheckpointHeader(HDF5HeaderData& a_header) const noexcept
909{
910 CH_TIME("ItoKMCStepper::writeCheckpointHeader");
911 if (m_verbosity > 5) {
912 pout() << m_name + "::writeCheckpointHeader" << endl;
913 }
914}
915#endif
916
917#ifdef CH_USE_HDF5
918template <typename I, typename C, typename R, typename F>
919void
920ItoKMCStepper<I, C, R, F>::readCheckpointHeader(HDF5HeaderData& a_header) noexcept
921{
922 CH_TIME("ItoKMCStepper::readCheckpointHeader");
923 if (m_verbosity > 5) {
924 pout() << m_name + "::readCheckpointHeader" << endl;
925 }
926}
927#endif
928
929#ifdef CH_USE_HDF5
930template <typename I, typename C, typename R, typename F>
931void
932ItoKMCStepper<I, C, R, F>::writeCheckpointData(HDF5Handle& a_handle, const int a_lvl) const noexcept
933{
934 CH_TIME("ItoKMCStepper::writeCheckpointData");
935 if (m_verbosity > 5) {
936 pout() << m_name + "::writeCheckpointData" << endl;
938
939 for (ItoIterator<ItoSolver> solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
940 solverIt()->writeCheckpointLevel(a_handle, a_lvl);
941 }
942
943 for (CdrIterator<CdrSolver> solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
944 solverIt()->writeCheckpointLevel(a_handle, a_lvl);
945 }
946
947 for (RtIterator<McPhoto> solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
948 solverIt()->writeCheckpointLevel(a_handle, a_lvl);
949 }
951 m_fieldSolver->writeCheckpointLevel(a_handle, a_lvl);
952 m_sigmaSolver->writeCheckpointLevel(a_handle, a_lvl);
953}
954#endif
955
956#ifdef CH_USE_HDF5
957template <typename I, typename C, typename R, typename F>
958void
959ItoKMCStepper<I, C, R, F>::readCheckpointData(HDF5Handle& a_handle, const int a_lvl) noexcept
960{
961 CH_TIME("ItoKMCStepper::readCheckpointData");
962 if (m_verbosity > 5) {
963 pout() << m_name + "::readCheckpointData" << endl;
965
966 for (ItoIterator<ItoSolver> solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
967 solverIt()->readCheckpointLevel(a_handle, a_lvl);
968 }
969
970 for (CdrIterator<CdrSolver> solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
971 solverIt()->readCheckpointLevel(a_handle, a_lvl);
972 }
973
974 for (RtIterator<McPhoto> solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
975 solverIt()->readCheckpointLevel(a_handle, a_lvl);
976 }
977
978 m_fieldSolver->readCheckpointLevel(a_handle, a_lvl);
979 m_sigmaSolver->readCheckpointLevel(a_handle, a_lvl);
980}
981#endif
982
983template <typename I, typename C, typename R, typename F>
984int
986{
987 CH_TIME("ItoKMCStepper::getNumberOfPlotVariables");
988 if (m_verbosity > 5) {
989 pout() << m_name + "::getNumberOfPlotVariables" << endl;
990 }
991
992 int numComp = 0;
993
994 // Ito solver variables.
995 for (ItoIterator<ItoSolver> solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
996 numComp += solverIt()->getNumberOfPlotVariables();
998
999 // Cdr solver variables
1000 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
1001 numComp += solverIt()->getNumberOfPlotVariables();
1002 }
1003
1004 // RTE solver variables.
1005 for (RtIterator<McPhoto> solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
1006 numComp += solverIt()->getNumberOfPlotVariables();
1007 }
1008
1009 // Field solver variables.
1010 numComp += m_fieldSolver->getNumberOfPlotVariables();
1011
1012 // Surface charge solver variables.
1013 numComp += m_sigmaSolver->getNumberOfPlotVariables();
1014
1015 // Conductivity
1016 if (m_plotConductivity) {
1017 numComp += 1;
1018 }
1019
1020 // Current density.
1021 if (m_plotCurrentDensity) {
1022 numComp += SpaceDim;
1023 }
1024
1025 // Number of particles per patch
1026 if (m_plotParticlesPerPatch) {
1027 numComp += 1;
1028 }
1029
1030 // Physics plot variables
1031 numComp += m_physics->getNumberOfPlotVariables();
1032
1033 return numComp;
1034}
1035
1036template <typename I, typename C, typename R, typename F>
1037Vector<std::string>
1040 CH_TIME("ItoKMCStepper::getPlotVariableNames");
1041 if (m_verbosity > 5) {
1042 pout() << m_name + "::getPlotVariableNames" << endl;
1043 }
1044
1045 Vector<std::string> plotVarNames;
1046
1047 plotVarNames.append(m_fieldSolver->getPlotVariableNames());
1048 plotVarNames.append(m_sigmaSolver->getPlotVariableNames());
1049
1050 for (ItoIterator<ItoSolver> solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
1051 plotVarNames.append(solverIt()->getPlotVariableNames());
1052 }
1053
1054 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
1055 plotVarNames.append(solverIt()->getPlotVariableNames());
1056 }
1057
1058 for (RtIterator<McPhoto> solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
1059 plotVarNames.append(solverIt()->getPlotVariableNames());
1060 }
1061
1062 // Write the conductivity to the output
1063 if (m_plotConductivity) {
1064 plotVarNames.push_back("Conductivity");
1065 }
1066
1067 // Write the current to the output
1068 if (m_plotCurrentDensity) {
1069 plotVarNames.push_back("x-J");
1070 plotVarNames.push_back("y-J");
1071 if (SpaceDim == 3) {
1072 plotVarNames.push_back("z-J");
1073 }
1074 }
1075
1076 // Write the number of particles per patch
1077 if (m_plotParticlesPerPatch) {
1078 plotVarNames.push_back("Particles per patch");
1079 }
1080
1081 // Physics plot variable names
1082 plotVarNames.append(m_physics->getPlotVariableNames());
1083
1084 return plotVarNames;
1085}
1086
1087template <typename I, typename C, typename R, typename F>
1088void
1089ItoKMCStepper<I, C, R, F>::writePlotData(LevelData<EBCellFAB>& a_output,
1090 int& a_icomp,
1091 const std::string& a_outputRealm,
1092 const int a_level) const noexcept
1093{
1094 CH_TIME("ItoKMCStepper::writePlotData");
1095 if (m_verbosity > 5) {
1096 pout() << m_name + "::writePlotData" << endl;
1097 }
1098
1099 // Poisson solver copies over its output data
1100 m_fieldSolver->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1102 // Surface charge solver writes
1103 m_sigmaSolver->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1104
1105 // Ito solvers copy their output data
1106 for (ItoIterator<ItoSolver> solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
1107 solverIt()->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1109
1110 // Cdr solvers output their data
1111 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
1112 solverIt()->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1113 }
1114
1115 // RTE solvers copy their output data
1116 for (RtIterator<McPhoto> solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
1117 solverIt()->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1118 }
1120 // Write the conductivity to the output
1121 if (m_plotConductivity) {
1122 this->writeData(a_output, a_icomp, m_conductivityCell, a_outputRealm, a_level, false, true);
1123 }
1124
1125 // Write the current to the output
1126 if (m_plotCurrentDensity) {
1127 this->writeData(a_output, a_icomp, m_currentDensity, a_outputRealm, a_level, false, true);
1128 }
1129
1130 // Write the number of particles per patch
1131 if (m_plotParticlesPerPatch) {
1132 this->writeNumberOfParticlesPerPatch(a_output, a_icomp, a_outputRealm, a_level);
1133 }
1134
1135 // Write physics plot variables
1136 if (m_physics->getNumberOfPlotVariables() > 0) {
1137 this->writeData(a_output, a_icomp, m_physicsPlotVariables, a_outputRealm, a_level, false, true);
1138 }
1139}
1140
1141template <typename I, typename C, typename R, typename F>
1142void
1143ItoKMCStepper<I, C, R, F>::writeData(LevelData<EBCellFAB>& a_output,
1144 int& a_comp,
1145 const EBAMRCellData& a_data,
1146 const std::string a_outputRealm,
1147 const int a_level,
1148 const bool a_interpToCentroids,
1149 const bool a_interpGhost) const noexcept
1150
1151{
1152 CH_TIMERS("ItoKMCStepper::writeData");
1153 CH_TIMER("ItoKMCStepper::writeData::allocate", t1);
1154 CH_TIMER("ItoKMCStepper::writeData::local_copy", t2);
1155 CH_TIMER("ItoKMCStepper::writeData::interp_ghost", t3);
1156 CH_TIMER("ItoKMCStepper::writeData::interp_centroid", t4);
1157 CH_TIMER("ItoKMCStepper::writeData::final_copy", t5);
1158 if (m_verbosity > 5) {
1159 pout() << m_name + "::writeData" << endl;
1160 }
1161
1162 // Number of components we are working with.
1163 const int numComp = a_data[a_level]->nComp();
1164
1165 // Component ranges that we copy to/from.
1166 const Interval srcInterv(0, numComp - 1);
1167 const Interval dstInterv(a_comp, a_comp + numComp - 1);
1168
1169 CH_START(t1);
1170 LevelData<EBCellFAB> scratch;
1171 m_amr->allocate(scratch, a_data.getRealm(), m_plasmaPhase, a_level, numComp);
1172 CH_STOP(t1);
1174 CH_START(t2);
1175 m_amr->copyData(scratch, *a_data[a_level], a_level, a_data.getRealm(), a_data.getRealm());
1176 CH_START(t2);
1177
1178 // Interpolate ghost cells
1179 CH_START(t3);
1180 if (a_level > 0 && a_interpGhost) {
1181 m_amr->interpGhost(scratch, *a_data[a_level - 1], a_level, a_data.getRealm(), m_plasmaPhase);
1182 }
1183 CH_STOP(t3);
1184
1185 CH_START(t4);
1186 if (a_interpToCentroids) {
1187 m_amr->interpToCentroids(scratch, a_data.getRealm(), m_plasmaPhase, a_level);
1188 }
1189 CH_STOP(t4);
1190
1191 DataOps::setCoveredValue(scratch, *m_amr->getCoveredCells(a_data.getRealm(), m_plasmaPhase)[a_level], 0.0);
1192
1193 CH_START(t5);
1194 m_amr->copyData(a_output,
1195 scratch,
1196 a_level,
1197 a_outputRealm,
1198 a_data.getRealm(),
1199 dstInterv,
1200 srcInterv,
1201 CopyStrategy::ValidGhost,
1202 CopyStrategy::ValidGhost);
1203 CH_STOP(t5);
1204
1205 a_comp += numComp;
1206}
1207
1208template <typename I, typename C, typename R, typename F>
1209void
1211 int& a_icomp,
1212 const std::string a_outputRealm,
1213 const int a_level) const noexcept
1214{
1215 CH_TIME("ItoKMCStepper::writeNumberOfParticlesPerPatch");
1216 if (m_verbosity > 5) {
1217 pout() << m_name + "::writeNumberOfParticlesPerPatch" << endl;
1218 }
1219
1220 CH_assert(a_level >= 0);
1221 CH_assert(a_level <= m_amr->getFinestLevel());
1222
1223 DataOps::setValue(*m_particleScratch1[a_level], 0.0, a_icomp);
1224
1225 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
1226 const ParticleContainer<ItoParticle>& particles = solverIt()->getParticles(ItoSolver::WhichContainer::Bulk);
1227
1228 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
1229 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
1230 const DataIterator& dit = dbl.dataIterator();
1231
1232 const int nbox = dit.size();
1233
1234#pragma omp parallel for schedule(runtime)
1235 for (int mybox = 0; mybox < nbox; mybox++) {
1236 const DataIndex& din = dit[mybox];
1237
1238 (*m_particleScratch1[lvl])[din] += particles[lvl][din].size();
1239 }
1240 }
1241 }
1242
1243 m_amr->copyData(a_output,
1244 *m_particleScratch1[a_level],
1245 a_level,
1246 a_outputRealm,
1247 m_particleRealm,
1248 Interval(a_icomp, a_icomp),
1249 Interval(0, 0));
1250
1251 a_icomp += 1;
1252}
1253
1254template <typename I, typename C, typename R, typename F>
1255void
1256ItoKMCStepper<I, C, R, F>::synchronizeSolverTimes(const int a_step, const Real a_time, const Real a_dt) noexcept
1258 CH_TIME("ItoKMCStepper::synchronizeSolverTimes");
1259 if (m_verbosity > 5) {
1260 pout() << m_name + "::synchronizeSolverTimes" << endl;
1261 }
1262
1263 m_timeStep = a_step;
1264 m_time = a_time;
1265 m_dt = a_dt;
1266
1267 m_ito->setTime(a_step, a_time, a_dt);
1268 m_fieldSolver->setTime(a_step, a_time, a_dt);
1269 m_rte->setTime(a_step, a_time, a_dt);
1270 m_sigmaSolver->setTime(a_step, a_time, a_dt);
1271}
1272
1273template <typename I, typename C, typename R, typename F>
1274void
1276{
1277 CH_TIME("ItoKMCStepper::printStepReport");
1278 if (m_verbosity > 5) {
1279 pout() << m_name + "::printStepReport" << endl;
1280 }
1282 const unsigned long long localParticlesBulk = m_ito->getNumParticles(ItoSolver::WhichContainer::Bulk, true);
1283 const unsigned long long globalParticlesBulk = m_ito->getNumParticles(ItoSolver::WhichContainer::Bulk, false);
1284 const unsigned long long localParticlesEB = m_ito->getNumParticles(ItoSolver::WhichContainer::EB, true);
1285 const unsigned long long globalParticlesEB = m_ito->getNumParticles(ItoSolver::WhichContainer::EB, false);
1286 const unsigned long long localParticlesDomain = m_ito->getNumParticles(ItoSolver::WhichContainer::Domain, true);
1287 const unsigned long long globalParticlesDomain = m_ito->getNumParticles(ItoSolver::WhichContainer::Domain, false);
1288 const unsigned long long localParticlesSource = m_ito->getNumParticles(ItoSolver::WhichContainer::Source, true);
1289 const unsigned long long globalParticlesSource = m_ito->getNumParticles(ItoSolver::WhichContainer::Source, false);
1290
1291 Real avgParticles = 0.0;
1292 Real stdDev = 0.0;
1293
1294 Real minParticles = 0.0;
1295 Real maxParticles = 0.0;
1296
1297 int minRank = 0;
1298 int maxRank = 0;
1299
1300 this->getParticleStatistics(avgParticles, stdDev, minParticles, maxParticles, minRank, maxRank);
1301
1302 Real maxDensity = -std::numeric_limits<Real>::max();
1303 Real minDensity = +std::numeric_limits<Real>::max();
1304
1305 std::string maxSolver = "invalid solver";
1306 std::string minSolver = "invalid solver";
1307
1308 this->getMaxMinRelativeItoDensity(maxDensity, minDensity, maxSolver, minSolver);
1309 this->getMaxMinRelativeCDRDensity(maxDensity, minDensity, maxSolver, minSolver);
1310
1311 std::string str;
1312 switch (m_timeCode) {
1313 case TimeCode::Physics: {
1314 str = "dt restricted by 'Physics'";
1315
1316 break;
1317 }
1318 case TimeCode::AdvectionIto: {
1319 str = "dt restricted by 'Advection (Ito)'";
1320
1321 break;
1322 }
1323 case TimeCode::DiffusionIto: {
1324 str = "dt restricted by 'Diffusion (Ito)'";
1325
1326 break;
1327 }
1328 case TimeCode::AdvectionDiffusionIto: {
1329 str = "dt restricted by 'AdvectionDiffusion (Ito)'";
1330
1331 break;
1332 }
1333 case TimeCode::AdvectionDiffusionCDR: {
1334 str = "dt restricted by 'AdvectionDiffusion (CDR)'";
1335
1336 break;
1337 }
1338 case TimeCode::RelaxationTime: {
1339 str = "dt restricted by 'Relaxation time'";
1340
1341 break;
1342 }
1343 case TimeCode::Hardcap: {
1344 str = "dt restricted by 'Hardcap'";
1345
1346 break;
1347 }
1348 default: {
1349 str = "dt restricted by 'Unspecified'";
1350
1351 break;
1352 }
1353 }
1354
1355 // Calculate the charge
1356 const Real Qplus = this->computeQplus();
1357 const Real Qminu = this->computeQminu();
1358 const Real Qsurf = this->computeQsurf();
1359 const Real Qtot = Qplus + Qminu + Qsurf;
1360
1361 // Print the step report.
1362
1363 //clang-format off
1364 const std::string whitespace = " ";
1365 pout() << " " + str << endl;
1366 pout() << whitespace + "Emax = " << m_maxReducedField << " (Td)" << endl
1367 << whitespace + "Max n/N = " << maxDensity << " (" << maxSolver << ")" << endl
1368 << whitespace + "Qplus = " << Qplus << endl
1369 << whitespace + "Qminu = " << Qminu << endl
1370 << whitespace + "Qsurf = " << Qsurf << endl
1371 << whitespace + "Qtot = " << Qtot << endl
1372 << whitespace + "CFL (Ito) = " << m_dt / m_particleAdvectionDiffusionDt << endl
1373 << whitespace + "CFL (CDR) = " << m_dt / m_fluidAdvectionDiffusionDt << endl
1374 << whitespace + "dt/dt_relax = " << m_dt / m_relaxationTime << endl
1375 << whitespace + "#Particles = " << DischargeIO::numberFmt(localParticlesBulk) << " ("
1376 << DischargeIO::numberFmt(globalParticlesBulk) << ")" << endl
1377 << whitespace + "#EB part. = " << DischargeIO::numberFmt(localParticlesEB) << " ("
1378 << DischargeIO::numberFmt(globalParticlesEB) << ")" << endl
1379 << whitespace + "#Dom. part. = " << DischargeIO::numberFmt(localParticlesDomain) << " ("
1380 << DischargeIO::numberFmt(globalParticlesDomain) << ")" << endl
1381 << whitespace + "#Src. part. = " << DischargeIO::numberFmt(localParticlesSource) << " ("
1382 << DischargeIO::numberFmt(globalParticlesSource) << ")" << endl
1383 << whitespace + "#Min part. = " << minParticles << " (on rank = " << minRank << ")" << endl
1384 << whitespace + "#Max part. = " << maxParticles << " (on rank = " << maxRank << ")" << endl
1385 << whitespace + "#Avg. part. = " << avgParticles << endl
1386 << whitespace + "#Dev. part. = " << stdDev << " (" << 100. * stdDev / avgParticles << "%)" << endl;
1387 //clang-format on
1388}
1389
1390template <typename I, typename C, typename R, typename F>
1391void
1393 Real& a_minDensity,
1394 std::string& a_maxSolver,
1395 std::string& a_minSolver) const noexcept
1397 CH_TIME("ItoKMCStepper::getMaxMinDensity(Realx2, std::string2x)");
1398 if (m_verbosity > 5) {
1399 pout() << m_name + "::getMaxMinDensity(Realx2, std::string2x)" << endl;
1400 }
1401
1402 // Scratch member rather than a per-call hierarchy -- printStepReport() runs every time step.
1403 EBAMRCellData& tmp = m_fluidScratch1;
1404
1405 // Go through each solver and find the max/min values.
1406 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
1407 const RefCountedPtr<ItoSolver>& solver = solverIt();
1408 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1409 const int Z = species->getChargeNumber();
1410
1411 if (Z != 0) {
1412 Real curMin = std::numeric_limits<Real>::max();
1413 Real curMax = -std::numeric_limits<Real>::max();
1414
1415 // Ito solvers might be defined on a separate realm, so we have to copy the data over
1416 // before dividing by the neutral density
1417 const Interval dstInterv = Interval(0, 0);
1418 const Interval srcInterv = Interval(0, 0);
1419
1420 m_amr->copyData(tmp, solverIt()->getPhi(), dstInterv, srcInterv);
1421
1422 DataOps::divideFallback(tmp, m_neutralDensity, 0.0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1423 DataOps::getMaxMin(curMax, curMin, tmp, 0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1424
1425 if (curMax > a_maxDensity) {
1426 a_maxDensity = curMax;
1427 a_maxSolver = solver->getName();
1428 }
1429
1430 if (curMin < a_minDensity) {
1431 a_minDensity = curMin;
1432 a_minSolver = solver->getName();
1434 }
1435 }
1436}
1437
1438template <typename I, typename C, typename R, typename F>
1439void
1441 Real& a_minDensity,
1442 std::string& a_maxSolver,
1443 std::string& a_minSolver) const noexcept
1444{
1445 CH_TIME("ItoKMCStepper::getMaxMinRelativeCDRDensity(Realx2, std::string2x)");
1446 if (m_verbosity > 5) {
1447 pout() << m_name + "::getMaxMinRelativeCDRDensity(Realx2, std::string2x)" << endl;
1448 }
1449
1450 // Scratch member rather than a per-call hierarchy -- printStepReport() runs every time step.
1451 EBAMRCellData& tmp = m_fluidScratch1;
1452
1453 // Go through each solver and find the max/min values.
1454 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
1455 const RefCountedPtr<CdrSolver>& solver = solverIt();
1456 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
1457 const int Z = species->getChargeNumber();
1458
1459 if (Z != 0) {
1460 Real curMin = std::numeric_limits<Real>::max();
1461 Real curMax = -std::numeric_limits<Real>::max();
1462
1463 // Copy the data over to a temporary holder before dividing by the neutral density
1464 const Interval dstInterv = Interval(0, 0);
1465 const Interval srcInterv = Interval(0, 0);
1466
1467 m_amr->copyData(tmp, solverIt()->getPhi(), dstInterv, srcInterv);
1468
1469 DataOps::divideFallback(tmp, m_neutralDensity, 0.0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1470 DataOps::getMaxMin(curMax, curMin, tmp, 0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1471
1472 if (curMax > a_maxDensity) {
1473 a_maxDensity = curMax;
1474 a_maxSolver = solver->getName();
1475 }
1476
1477 if (curMin < a_minDensity) {
1478 a_minDensity = curMin;
1479 a_minSolver = solver->getName();
1480 }
1481 }
1483}
1484
1485template <typename I, typename C, typename R, typename F>
1486void
1488 Real& a_sigma,
1489 Real& a_minParticles,
1490 Real& a_maxParticles,
1491 int& a_minRank,
1492 int& a_maxRank)
1493{
1494 CH_TIME("ItoKMCStepper::getParticleStatistics");
1495 if (m_verbosity > 5) {
1496 pout() << m_name + "::getParticleStatistics" << endl;
1497 }
1498
1499 // TLDR: We compute the number of particles, the standard deviation of the number of particles, as well
1500 // as the ranks having the smallest/largest number of particles.
1501
1502 const Real numParticles = 1.0 * m_ito->getNumParticles(ItoSolver::WhichContainer::Bulk, true);
1503
1504 const std::pair<Real, int> minParticles = ParallelOps::minRank(numParticles);
1505 const std::pair<Real, int> maxParticles = ParallelOps::maxRank(numParticles);
1506
1507 a_avgParticles = ParallelOps::average(numParticles);
1508 a_sigma = ParallelOps::standardDeviation(numParticles);
1509
1510 a_minParticles = minParticles.first;
1511 a_maxParticles = maxParticles.first;
1512
1513 a_minRank = minParticles.second;
1514 a_maxRank = maxParticles.second;
1515}
1516
1517template <typename I, typename C, typename R, typename F>
1518Real
1520{
1521 CH_TIME("ItoKMCStepper::computeDt");
1522 if (m_verbosity > 5) {
1523 pout() << m_name + "::computeDt" << endl;
1524 }
1525
1526 Timer timer(m_name + "::computeDt");
1527
1528 Real dt = std::numeric_limits<Real>::max();
1529
1530 const Real maxGrowthDt = m_prevDt > 0.0 ? m_prevDt * m_maxGrowthDt : dt;
1531 const Real minShrinkDt = m_prevDt > 0.0 ? m_prevDt / m_maxShrinkDt : 0.0;
1532
1533 if (m_timeStep == 0) {
1534 this->computeDummyPhysicsDt();
1535 }
1536
1537 // Compute various time steps.
1538 timer.startEvent("Advection (Ito)");
1539 m_particleAdvectionDt = m_ito->computeAdvectiveDt();
1540 timer.stopEvent("Advection (Ito)");
1541
1542 timer.startEvent("Diffusion (Ito)");
1543 m_particleDiffusionDt = m_ito->computeDiffusiveDt();
1544 timer.stopEvent("Diffusion (Ito)");
1545
1546 timer.startEvent("AdvectionDiffusion (Ito)");
1547 m_particleAdvectionDiffusionDt = m_ito->computeDt();
1548 timer.stopEvent("AdvectionDiffusion (Ito)");
1549
1550 timer.startEvent("AdvectionDiffusion (CDR)");
1551 m_fluidAdvectionDiffusionDt = m_cdr->computeAdvectionDiffusionDt();
1552 timer.stopEvent("AdvectionDiffusion (CDR)");
1553
1554 timer.startEvent("Relaxation");
1555 m_relaxationTime = this->computeRelaxationTime();
1556 timer.stopEvent("Relaxation");
1558 const bool hasParticleAdvectionDt = m_particleAdvectionDt < std::numeric_limits<Real>::max();
1559 const bool hasParticleDiffusionDt = m_particleDiffusionDt < std::numeric_limits<Real>::max();
1560 const bool hasParticleAdvectionDiffusionDt = m_particleAdvectionDiffusionDt < std::numeric_limits<Real>::max();
1561
1562 if (m_maxParticleAdvectionCFL * m_particleAdvectionDt < dt) {
1563 dt = m_maxParticleAdvectionCFL * m_particleAdvectionDt;
1564 m_timeCode = TimeCode::AdvectionIto;
1565 }
1566
1567 if (m_maxParticleDiffusionCFL * m_particleDiffusionDt < dt) {
1568 dt = m_maxParticleDiffusionCFL * m_particleDiffusionDt;
1569 m_timeCode = TimeCode::DiffusionIto;
1570 }
1571
1572 if (m_maxParticleAdvectionDiffusionCFL * m_particleAdvectionDiffusionDt < dt) {
1573 dt = m_maxParticleAdvectionDiffusionCFL * m_particleAdvectionDiffusionDt;
1574 m_timeCode = TimeCode::AdvectionDiffusionIto;
1575 }
1576
1577 if (std::min(m_fluidAdvectionDiffusionCFL, 0.9) * m_fluidAdvectionDiffusionDt < dt) {
1578 dt = std::min(m_fluidAdvectionDiffusionCFL, 0.9) * m_fluidAdvectionDiffusionDt;
1579 m_timeCode = TimeCode::AdvectionDiffusionCDR;
1580 }
1581
1582 if (m_relaxTimeFactor * m_relaxationTime < dt) {
1583 dt = m_relaxTimeFactor * m_relaxationTime;
1584 m_timeCode = TimeCode::RelaxationTime;
1585 }
1586
1587 if (m_physicsDtFactor * m_physicsDt < dt) {
1588 dt = m_physicsDtFactor * m_physicsDt;
1589 m_timeCode = TimeCode::Physics;
1590 }
1591
1592 if ((dt < m_minParticleAdvectionCFL * m_particleAdvectionDt) && hasParticleAdvectionDt) {
1593 dt = m_minParticleAdvectionCFL * m_particleAdvectionDt;
1594 m_timeCode = TimeCode::AdvectionIto;
1596
1597 if ((dt < m_minParticleDiffusionCFL * m_particleDiffusionDt) && hasParticleDiffusionDt) {
1598 dt = m_minParticleDiffusionCFL * m_particleDiffusionDt;
1599 m_timeCode = TimeCode::DiffusionIto;
1600 }
1601
1602 if ((dt < m_minParticleAdvectionDiffusionCFL * m_particleAdvectionDiffusionDt) && hasParticleAdvectionDiffusionDt) {
1603 dt = m_minParticleAdvectionDiffusionCFL * m_particleAdvectionDiffusionDt;
1604 m_timeCode = TimeCode::AdvectionDiffusionIto;
1605 }
1606
1607 if (dt > maxGrowthDt) {
1608 dt = maxGrowthDt;
1609 }
1610
1611 if (dt < minShrinkDt) {
1612 dt = minShrinkDt;
1613 }
1614
1615 if (m_minDt > dt) {
1616 dt = m_minDt;
1617 m_timeCode = TimeCode::Hardcap;
1618 }
1619
1620 if (m_maxDt < dt) {
1621 dt = m_maxDt;
1622 m_timeCode = TimeCode::Hardcap;
1623 }
1625 if (m_profile) {
1626 timer.eventReport(pout(), false);
1627 }
1628
1629 return dt;
1630}
1631
1632template <typename I, typename C, typename R, typename F>
1633void
1635{
1636 CH_TIME("ItoKMCStepper::registerRealms");
1637 if (m_verbosity > 5) {
1638 pout() << m_name + "::registerRealms" << endl;
1639 }
1640
1641 // TLDR: If using dual grid then m_particleRealm != m_fluidRealm and we'll have two realms.
1642 m_amr->registerRealm(m_fluidRealm);
1643 m_amr->registerRealm(m_particleRealm);
1644}
1645
1646template <typename I, typename C, typename R, typename F>
1647void
1649{
1650 CH_TIME("ItoKMCStepper::registerOperators");
1651 if (m_verbosity > 5) {
1652 pout() << m_name + "::registerOperators" << endl;
1653 }
1654
1655 m_ito->registerOperators();
1656 m_cdr->registerOperators();
1657 m_fieldSolver->registerOperators();
1658 m_rte->registerOperators();
1659 m_sigmaSolver->registerOperators();
1660
1661 // Required for non-cell-based particle mergers.
1662 m_amr->registerParticleGhostMask(m_particleRealm, 1);
1663}
1664
1665template <typename I, typename C, typename R, typename F>
1666void
1668{
1669 CH_TIME("ItoKMCStepper::prePlot");
1670 if (m_verbosity > 5) {
1671 pout() << m_name + "::prePlot" << endl;
1672 }
1673
1674 const int numPhysicsPlotVars = m_physics->getNumberOfPlotVariables();
1675
1676 if (numPhysicsPlotVars > 0) {
1677 m_amr->allocate(m_physicsPlotVariables, m_fluidRealm, m_plasmaPhase, numPhysicsPlotVars);
1678
1679 this->computePhysicsPlotVariables(m_physicsPlotVariables);
1680 }
1681
1682 this->computeCurrentDensity(this->m_currentDensity);
1683 m_ito->depositParticles();
1684}
1685
1686template <typename I, typename C, typename R, typename F>
1687void
1689{
1690 CH_TIME("ItoKMCStepper::postPlot");
1691 if (m_verbosity > 5) {
1692 pout() << m_name + "::postPlot" << endl;
1693 }
1695 m_physicsPlotVariables.clear();
1696}
1697
1698template <typename I, typename C, typename R, typename F>
1699void
1700ItoKMCStepper<I, C, R, F>::preRegrid(const int a_lmin, const int a_oldFinestLevel) noexcept
1701{
1702 CH_TIME("ItoKMCStepper::preRegrid");
1703 if (m_verbosity > 5) {
1704 pout() << m_name + "::preRegrid" << endl;
1705 }
1706
1707 const int numItoSpecies = m_physics->getNumItoSpecies();
1708 const int numCdrSpecies = m_physics->getNumCdrSpecies();
1709 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
1710 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
1711
1712 // If we are load balancing then we need to store the number of particles per cell on the old grids. This
1713 // will be used to estimate computational loads on the new grids.
1714 //
1715 // ORDERING: this must stay ahead of m_ito->preRegrid() below. That call extracts the bulk particles
1716 // into the solver's reduced regrid holder and leaves the ItoParticle container empty, so reading
1717 // getParticles(Bulk) afterwards would deposit nothing and every load would come back zero -- which
1718 // produces a perfectly valid, perfectly unbalanced layout rather than an error.
1719 if (m_loadBalanceParticles) {
1720 Vector<RefCountedPtr<ItoSolver>> lbSolvers = this->getLoadBalanceSolvers();
1721
1722 m_loadBalancePPC.resize(lbSolvers.size());
1723
1724 // Allocate and compute number of computational particles per cell.
1725 for (int i = 0; i < lbSolvers.size(); i++) {
1726 m_amr->allocate(m_loadBalancePPC[i], m_particleRealm, m_plasmaPhase, 1);
1727
1728 EBAMRCellData& compPPC = m_loadBalancePPC[i];
1729 const ParticleContainer<ItoParticle>& particles = lbSolvers[i]->getParticles(ItoSolver::WhichContainer::Bulk);
1730
1733 }
1734
1735 // Release some unnecessary storage.
1736 m_fluidScratch1.clear();
1737 m_fluidScratchD.clear();
1738 m_fluidScratchEB.clear();
1739
1740 for (int i = 0; i < m_fluidScratchIto.size(); i++) {
1741 m_fluidScratchIto[i].clear();
1742 }
1743
1744 m_particleScratch1.clear();
1745 m_particleScratchD.clear();
1746 m_particleScratchEB.clear();
1748 m_conductivityCell.clear();
1749 m_conductivityFace.clear();
1750 m_conductivityEB.clear();
1751
1752 m_electricFieldParticle.clear();
1753 m_electricFieldFluid.clear();
1754
1755 m_electricFieldParticle.clear();
1756 m_electricFieldFluid.clear();
1757
1758 for (int i = 0; i < numCdrSpecies; i++) {
1759 m_cdrMobilities[i].clear();
1760 m_cdrProducts[i]->clearParticles();
1761 }
1763 for (int i = 0; i < numItoSpecies; i++) {
1764 m_fluidGradPhiIto[i].clear();
1765 m_fluidPhiIto[i].clear();
1766 }
1767 for (int i = 0; i < numCdrSpecies; i++) {
1768 m_fluidGradPhiCDR[i].clear();
1769 }
1771 for (int i = 0; i < numItoSpecies; i++) {
1772 m_secondaryParticles[i]->clearParticles();
1773 }
1774 for (int i = 0; i < numPhotonSpecies; i++) {
1775 m_secondaryPhotons[i]->clearParticles();
1776 }
1778 for (int i = 0; i < numCdrSpecies; i++) {
1779 m_cdrFluxes[i].clear();
1780 m_cdrFluxesExtrap[i].clear();
1781 }
1782
1783 m_currentDensity.clear();
1784 m_fluidPPC.clear();
1785
1786 m_particleItoPPC.clear();
1787 m_particleOldItoPPC.clear();
1788
1789 if (numCdrSpecies > 0) {
1790 m_fluidCdrPPC.clear();
1791 m_fluidOldCdrPPC.clear();
1792 }
1793
1794 // Unconditional: unlike the two above, this one holds real data even when there are no CDR species.
1795 m_particleCdrProduction.clear();
1796
1797 m_particleYPC.clear();
1798 m_fluidYPC.clear();
1799
1800 // Put solvers in pre-regrid mode.
1801 m_ito->preRegrid(a_lmin, a_oldFinestLevel);
1802 m_cdr->preRegrid(a_lmin, a_oldFinestLevel);
1803 m_fieldSolver->preRegrid(a_lmin, a_oldFinestLevel);
1804 m_rte->preRegrid(a_lmin, a_oldFinestLevel);
1805 m_sigmaSolver->preRegrid(a_lmin, a_oldFinestLevel);
1806}
1807
1808template <typename I, typename C, typename R, typename F>
1809void
1810ItoKMCStepper<I, C, R, F>::regrid(const int a_lmin, const int a_oldFinestLevel, const int a_newFinestLevel) noexcept
1811{
1812 CH_TIME("ItoKMCStepper::regrid");
1813 if (m_verbosity > 5) {
1814 pout() << m_name + "::regrid" << endl;
1815 }
1816
1817 this->allocateInternals();
1818
1819 m_ito->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1820 m_cdr->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1821 m_fieldSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1822 m_rte->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1823 m_sigmaSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1824
1825 // The regrid super-particle merge now runs inside ItoSolver::regrid(), on the reduced particles and
1826 // before they are rebuilt as ItoParticles -- which is the whole point: the de-refinement pile-up is
1827 // merged away while it is still 53 B per particle. See ItoSolver.regrid_superparticles.
1828
1829 // Redeposit particles and update the electric field on the new mesh.
1830 m_ito->depositParticles();
1831
1832 const bool converged = this->solvePoisson();
1833 if (!converged) {
1834 const std::string err = "ItoKMCStepper::regrid - Poisson solve did not converge after regrid!!!";
1836 if (m_abortOnFailure) {
1837 MayDay::Error(err.c_str());
1838 }
1839 else {
1840 MayDay::Warning(err.c_str());
1842 }
1843
1844 this->computeDriftVelocities();
1845 this->computeDiffusionCoefficients();
1846
1847 this->fillNeutralDensity();
1849
1850template <typename I, typename C, typename R, typename F>
1851void
1853{
1854 CH_TIME("ItoKMCStepper::postRegrid");
1855
1856 if (m_loadBalanceParticles) {
1857 for (int i = 0; i < m_loadBalancePPC.size(); i++) {
1858 m_amr->deallocate(m_loadBalancePPC[i]);
1859 }
1861}
1862
1863template <typename I, typename C, typename R, typename F>
1864void
1865ItoKMCStepper<I, C, R, F>::setVoltage(const std::function<Real(const Real a_time)>& a_voltage) noexcept
1867 CH_TIME("ItoKMCStepper::setVoltage");
1868 if (m_verbosity > 5) {
1869 pout() << m_name + "::setVoltage" << endl;
1870 }
1871
1872 m_voltage = a_voltage;
1873}
1874
1875template <typename I, typename C, typename R, typename F>
1876void
1878{
1879 CH_TIME("ItoKMCStepper::fillNeutralDensity");
1880 if (m_verbosity > 5) {
1881 pout() << m_name + "::fillNeutralDensity" << endl;
1883
1884 DataOps::setValue(m_neutralDensity, std::numeric_limits<Real>::max());
1885
1886 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
1887 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[lvl];
1888 const EBISLayout& ebisl = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[lvl];
1889 const DataIterator& dit = dbl.dataIterator();
1890 const Real dx = m_amr->getDx()[lvl];
1891 const RealVect probLo = m_amr->getProbLo();
1892
1893 const int nbox = dit.size();
1895 CH_assert(!(m_neutralDensity[lvl].isNull()));
1896 CH_assert(m_neutralDensity[lvl]->nComp() == 1);
1897
1898#pragma omp parallel for schedule(runtime)
1899 for (int mybox = 0; mybox < nbox; mybox++) {
1900 const DataIndex& din = dit[mybox];
1901 const Box cellBox = dbl[din];
1902 const EBISBox& ebisbox = ebisl[din];
1903
1904 EBCellFAB& neutralDensity = (*m_neutralDensity[lvl])[din];
1905 FArrayBox& neutralDensityReg = neutralDensity.getFArrayBox();
1907 auto regularKernel = [&](const IntVect& iv) -> void {
1908 const RealVect pos = probLo + (0.5 * RealVect::Unit + iv) * dx;
1909
1910 neutralDensityReg(iv, 0) = m_physics->getNeutralDensity(pos);
1911 };
1912
1913 auto irregularKernel = [&](const VolIndex& vof) -> void {
1914 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
1915
1916 neutralDensity(vof, 0) = m_physics->getNeutralDensity(pos);
1917 };
1918
1919 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[lvl])[din];
1920
1921 // Not vectorizable: m_physics->getNeutralDensity(pos) is a virtual call per cell. One-time setup.
1922 // Multi-cut N/A (center vs centroid position).
1923 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
1924 BoxLoops::loop(vofit, irregularKernel);
1925 }
1926 }
1927
1928 m_amr->conservativeAverage(m_neutralDensity, m_fluidRealm, m_plasmaPhase);
1929 m_amr->interpGhostPwl(m_neutralDensity, m_fluidRealm, m_plasmaPhase);
1930}
1931
1932template <typename I, typename C, typename R, typename F>
1933Real
1935{
1936 CH_TIME("ItoKMCStepper::computeMaxReducedElectricField");
1937 if (m_verbosity > 5) {
1938 pout() << m_name + "::computeMaxReducedElectricField" << endl;
1939 }
1940
1941 // The body below interpolates and divides on the plasma phase regardless of a_phase, and m_fluidScratch1
1942 // lives on the plasma phase, so the two have to agree.
1943 CH_assert(a_phase == m_plasmaPhase);
1944
1945 // Get a handle to the E-field. Note that this is the cell-centered field!
1946 const EBAMRCellData cellCenteredE = m_amr->alias(a_phase, m_fieldSolver->getElectricField());
1947
1948 // Interpolate to centroids. Runs through the scratch member rather than a per-call hierarchy: this is
1949 // called once per time step, and AmrMesh::allocate defines an exchange Copier per level.
1950 EBAMRCellData& tmp = m_fluidScratch1;
1951
1953 cellCenteredE,
1954 m_amr->getNotCoveredCells(m_fluidRealm, a_phase),
1955 m_amr->getMultiCutVofIterator(m_fluidRealm, a_phase));
1956 m_amr->interpToCentroids(tmp, m_fluidRealm, m_plasmaPhase);
1957
1958 DataOps::divideFallback(tmp, m_neutralDensity, 0.0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1959
1960 Real max = 0.0;
1961 Real min = 0.0;
1962
1963 DataOps::getMaxMin(max, min, tmp, 0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
1964
1965 return max * 1E21;
1966}
1967
1968template <typename I, typename C, typename R, typename F>
1969void
1971 const phase::which_phase a_phase) const noexcept
1972{
1973 CH_TIME("ItoKMCStepper::computeElectricField(EBAMRCellData, phase)");
1974 if (m_verbosity > 5) {
1975 pout() << m_name + "::computeElectricField(EBAMRCellData, phase)" << endl;
1976 }
1977
1978 CH_assert(a_electricField.getRealm() == m_fluidRealm);
1979
1980 m_fieldSolver->computeElectricField(a_electricField, a_phase, m_fieldSolver->getPotential());
1981}
1982
1983template <typename I, typename C, typename R, typename F>
1984Real
1986{
1987 CH_TIME("ItoKMCStepper::getTime");
1988 if (m_verbosity > 5) {
1989 pout() << m_name + "::getTime" << endl;
1990 }
1991
1992 return m_time;
1993}
1994
1995template <typename I, typename C, typename R, typename F>
1996void
1998{
1999 CH_TIME("ItoKMCStepper::computeSpaceChargeDensity()");
2000 if (m_verbosity > 5) {
2001 pout() << m_name + "::computeSpaceChargeDensity()" << endl;
2002 }
2003
2004 this->computeSpaceChargeDensity(m_fieldSolver->getRho(), m_ito->getDensities(), m_cdr->getPhis());
2005}
2006
2007template <typename I, typename C, typename R, typename F>
2008void
2010 const Vector<EBAMRCellData*>& a_itoDensities,
2011 const Vector<EBAMRCellData*>& a_cdrDensities) noexcept
2012{
2013 CH_TIME("ItoKMCStepper::computeSpaceChargeDensity(rho, densities)");
2014 if (m_verbosity > 5) {
2015 pout() << m_name + "::computeSpaceChargeDensity(rho, densities)" << endl;
2016 }
2017
2018 // Guard the realm spot-checks: either layout may legitimately be empty. A chemistry that puts every
2019 // plasma species on the Ito solver leaves a_cdrDensities empty, and indexing [0] to check its realm
2020 // then aborts on the assert rather than on anything real. The loops below iterate the solver
2021 // layouts, so an empty one is already a no-op there.
2022 CH_assert(a_rho.getRealm() == m_fluidRealm);
2023 CH_assert(a_itoDensities.size() == 0 || a_itoDensities[0]->getRealm() == m_particleRealm);
2024 CH_assert(a_cdrDensities.size() == 0 || a_cdrDensities[0]->getRealm() == m_fluidRealm);
2025
2026 // TLDR: a_itoDensities could be defined over the particle realm, so we use m_fluidScratch1 as a temporary storage.
2027
2028 DataOps::setValue(a_rho, 0.0);
2029
2030 // Alias for the plasma phase.
2031 EBAMRCellData rhoPhase = m_amr->alias(m_plasmaPhase, a_rho);
2032
2033 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2034 const RefCountedPtr<ItoSolver>& solver = solverIt();
2035 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2036 const int idx = solverIt.index();
2037 const int Z = species->getChargeNumber();
2038
2039 if (Z != 0) {
2040 m_amr->copyData(m_fluidScratch1, *a_itoDensities[idx]);
2041
2042 DataOps::incr(rhoPhase, m_fluidScratch1, 1.0 * Z);
2043 }
2044 }
2045
2046 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
2047 const RefCountedPtr<CdrSolver>& solver = solverIt();
2048 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
2049 const int idx = solverIt.index();
2050 const int Z = species->getChargeNumber();
2051
2052 if (Z != 0) {
2053 DataOps::incr(rhoPhase, *a_cdrDensities[idx], 1.0 * Z);
2054 }
2055 }
2056
2057 DataOps::scale(a_rho, Units::Qe);
2058
2059 m_amr->arithmeticAverage(a_rho, m_fluidRealm);
2060 m_amr->interpGhostPwl(a_rho, m_fluidRealm);
2061
2062 // Interpolate to centroids.
2063 m_amr->interpToCentroids(rhoPhase, m_fluidRealm, m_plasmaPhase);
2064}
2065
2066template <typename I, typename C, typename R, typename F>
2067void
2068ItoKMCStepper<I, C, R, F>::computeConductivityCell(EBAMRCellData& a_conductivity) noexcept
2069{
2070 CH_TIME("ItoKMCStepper::computeConductivityCell(EBAMRCellData)");
2071 if (m_verbosity > 5) {
2072 pout() << m_name + "::computeConductivityCell(EBAMRCellData)" << endl;
2073 }
2074
2075 this->computeConductivityCell(a_conductivity, m_ito->getParticles(ItoSolver::WhichContainer::Bulk));
2076}
2077
2078template <typename I, typename C, typename R, typename F>
2079void
2081 const Vector<ParticleContainer<ItoParticle>*>& a_particles) noexcept
2082{
2083 CH_TIME("ItoKMCStepper::computeConductivityCell(EBAMRCellData, Particles)");
2084 if (m_verbosity > 5) {
2085 pout() << m_name + "::computeConductivityCell(EBAMRCellData, Particles)" << endl;
2086 }
2087
2088 DataOps::setValue(a_conductivity, 0.0);
2089
2090 // Add contribution from particle solvers
2091 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2092 RefCountedPtr<ItoSolver>& solver = solverIt();
2093 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2094
2095 const int idx = solverIt.index();
2096 const int Z = species->getChargeNumber();
2097
2098 if (Z != 0 && solver->isMobile()) {
2099 solver->depositConductivity(m_particleScratch1, *a_particles[idx]);
2100
2101 // Add to the fluid realm.
2102 m_amr->copyData(m_fluidScratch1, m_particleScratch1);
2103 DataOps::incr(a_conductivity, m_fluidScratch1, 1.0 * std::abs(Z));
2104 }
2105 }
2106
2107 // Add contribution from CDR solvers
2108 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
2109 const RefCountedPtr<CdrSolver>& solver = solverIt();
2110 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
2111
2112 const int idx = solverIt.index();
2113 const int Z = species->getChargeNumber();
2114
2115 if (Z != 0 && solver->isMobile()) {
2116 const EBAMRCellData& phi = solver->getPhi();
2117 const EBAMRCellData& mobility = m_cdrMobilities[idx];
2118
2119 DataOps::copy(m_fluidScratch1, phi);
2120 DataOps::multiplyScalar(m_fluidScratch1, mobility);
2121 DataOps::incr(a_conductivity, m_fluidScratch1, 1.0 * std::abs(Z));
2122 }
2123 }
2124
2125 DataOps::scale(a_conductivity, Units::Qe);
2126
2127 m_amr->arithmeticAverage(a_conductivity, m_fluidRealm, m_plasmaPhase);
2128 m_amr->interpGhostPwl(a_conductivity, m_fluidRealm, m_plasmaPhase);
2129
2130 // Interpolate to centroids.
2131 m_amr->interpToCentroids(a_conductivity, m_fluidRealm, m_plasmaPhase);
2132}
2133
2134template <typename I, typename C, typename R, typename F>
2135void
2137{
2138 CH_TIME("ItoKMCStepper::computeDensityGradients()");
2139 if (m_verbosity > 5) {
2140 pout() << m_name + "::computeDensityGradients()" << endl;
2141 }
2142
2143 const int numItoSpecies = m_physics->getNumItoSpecies();
2144
2145 // Each gradient costs a cross-realm copy, a coarsening, a ghost interpolation and the gradient stencil
2146 // itself, over the whole hierarchy. The reaction kernel reads a gradient slot for every species, but a
2147 // chemistry typically corrects on one or two of them; the rest were computed and never looked at. Ask
2148 // the physics which ones it actually reads, and zero the others so the unread slots hold a defined
2149 // value rather than whatever the last grid left there.
2150 //
2151 // The plasma index is the Ito species first and then the CDR species, matching the density vector that
2152 // updateReactionRates() is handed.
2153
2154 // Do the same for the Ito species.
2155 for (auto it = m_ito->iterator(); it.ok(); ++it) {
2156 const RefCountedPtr<ItoSolver>& solver = it();
2157
2158 const int idx = it.index();
2159
2160 if (!(m_physics->needGradient(idx))) {
2161 DataOps::setValue(m_fluidGradPhiIto[idx], 0.0);
2162
2163 continue;
2164 }
2165
2166 // Update ghost cells and coarsenings. Then compute the gradient.
2167 m_amr->copyData(m_fluidPhiIto[idx], solver->getPhi());
2168
2169 m_amr->arithmeticAverage(m_fluidPhiIto[idx], m_fluidRealm, m_plasmaPhase);
2170 m_amr->interpGhostPwl(m_fluidPhiIto[idx], m_fluidRealm, m_plasmaPhase);
2171
2172 m_amr->computeGradient(m_fluidGradPhiIto[idx], m_fluidPhiIto[idx], m_fluidRealm, m_plasmaPhase);
2173 }
2174
2175 // Compute gradients for the CDR species.
2176 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
2177 const RefCountedPtr<CdrSolver>& solver = it();
2178
2179 const int idx = it.index();
2180
2181 if (!(m_physics->needGradient(numItoSpecies + idx))) {
2182 DataOps::setValue(m_fluidGradPhiCDR[idx], 0.0);
2183
2184 continue;
2185 }
2186
2187 // Update ghost cells and coarsenings. Then compute the gradient.
2188 m_amr->copyData(m_fluidScratch1, solver->getPhi());
2189
2190 m_amr->arithmeticAverage(m_fluidScratch1, m_fluidRealm, m_plasmaPhase);
2191 m_amr->interpGhostPwl(m_fluidScratch1, m_fluidRealm, m_plasmaPhase);
2192
2193 m_amr->computeGradient(m_fluidGradPhiCDR[idx], m_fluidScratch1, m_fluidRealm, m_plasmaPhase);
2194 }
2195}
2196
2197template <typename I, typename C, typename R, typename F>
2198void
2200{
2201 CH_TIME("ItoKMCStepper::computeCurrentDensity(EBAMRCellData)");
2202 if (m_verbosity > 5) {
2203 pout() << m_name + "::computeCurrentDensity(EBAMRCellData)" << endl;
2204 }
2205
2206 CH_assert(a_J[0]->nComp() == SpaceDim);
2207
2208 EBAMRCellData conductivity;
2209 m_amr->allocate(conductivity, m_fluidRealm, m_plasmaPhase, 1);
2210 this->computeConductivityCell(conductivity);
2211
2212 DataOps::copy(a_J, m_electricFieldFluid);
2213 DataOps::multiplyScalar(a_J, conductivity);
2214}
2215
2216template <typename I, typename C, typename R, typename F>
2217Real
2219{
2220 CH_TIME("ItoKMCStepper::computeRelaxationTime()");
2221 if (m_verbosity > 5) {
2222 pout() << m_name + "::computeRelaxationTime()" << endl;
2223 }
2224
2225 // TLDR: We compute eps0/conductivity directly.
2226
2227 EBAMRCellData conductivity;
2228 EBAMRCellData relaxTime;
2229
2230 m_amr->allocate(conductivity, m_fluidRealm, m_plasmaPhase, 1);
2231 m_amr->allocate(relaxTime, m_fluidRealm, m_plasmaPhase, 1);
2232
2233 this->computeConductivityCell(conductivity);
2234
2235 DataOps::setValue(relaxTime, Units::eps0);
2236 DataOps::divideFallback(relaxTime,
2237 conductivity,
2238 std::numeric_limits<Real>::max(),
2239 m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
2240
2241 m_amr->conservativeAverage(relaxTime, m_fluidRealm, m_plasmaPhase);
2242
2243 Real min = std::numeric_limits<Real>::max();
2244 Real max = -std::numeric_limits<Real>::max();
2245
2246 DataOps::getMaxMinNorm(max, min, relaxTime, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
2247
2248 return min;
2249}
2250
2251template <typename I, typename C, typename R, typename F>
2252bool
2254{
2255 CH_TIME("ItoKMCStepper::solvePoisson()");
2256 if (m_verbosity > 5) {
2257 pout() << m_name + "::solvePoisson()" << endl;
2258 }
2259
2260 // Solve the Poisson equation and compute the cell-centered electric field.
2261 MFAMRCellData& phi = m_fieldSolver->getPotential();
2262 MFAMRCellData& rho = m_fieldSolver->getRho();
2263 EBAMRIVData& sigma = m_sigmaSolver->getPhi();
2264
2265 const bool converged = m_fieldSolver->solve(phi, rho, sigma, false);
2266
2267 m_fieldSolver->computeElectricField();
2268
2269 // Copy the electric field to appropriate data holders and perform center-to-centroid
2270 // interpolation.
2271 EBAMRCellData E;
2272 m_amr->allocatePointer(E, m_fluidRealm);
2273 m_amr->alias(E, m_plasmaPhase, m_fieldSolver->getElectricField());
2274
2275 // Fluid realm
2276 m_amr->copyData(m_electricFieldFluid, E);
2277 m_amr->conservativeAverage(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
2278 m_amr->interpGhostPwl(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
2279 m_amr->interpToCentroids(m_electricFieldFluid, m_fluidRealm, m_plasmaPhase);
2280
2281 // Particle realm
2282 m_amr->copyData(m_electricFieldParticle, E);
2283 m_amr->conservativeAverage(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
2284 m_amr->interpGhostPwl(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
2285 m_amr->interpToCentroids(m_electricFieldParticle, m_particleRealm, m_plasmaPhase);
2286
2287 return converged;
2288}
2289
2290template <typename I, typename C, typename R, typename F>
2291void
2293 const SpeciesSubset a_speciesSubset,
2294 const bool a_delete,
2295 const std::function<void(ParticleSoA<ItoParticle>&, std::size_t)> a_nonDeletionModifier) noexcept
2296{
2297 CH_TIME("ItoKMCStepper::intersectParticles(SpeciesSubset, bool, std::function)");
2298 if (m_verbosity > 5) {
2299 pout() << m_name + "::intersectParticles(SpeciesSubset, bool, std::function)" << endl;
2300 }
2301
2302 this->intersectParticles(a_speciesSubset,
2303 ItoSolver::WhichContainer::Bulk,
2304 ItoSolver::WhichContainer::EB,
2305 ItoSolver::WhichContainer::Domain,
2306 a_delete,
2307 a_nonDeletionModifier);
2308}
2309
2310template <typename I, typename C, typename R, typename F>
2311void
2313 const SpeciesSubset a_speciesSubset,
2314 const ItoSolver::WhichContainer a_containerBulk,
2315 const ItoSolver::WhichContainer a_containerEB,
2316 const ItoSolver::WhichContainer a_containerDomain,
2317 const bool a_delete,
2318 const std::function<void(ParticleSoA<ItoParticle>&, std::size_t)> a_nonDeletionModifier) noexcept
2319{
2320 CH_TIME("ItoKMCStepper::intersectParticles(SpeciesSubset, Containerx3, bool, std::function)");
2321 if (m_verbosity > 5) {
2322 pout() << m_name + "::intersectParticles(SpeciesSubset, Containerx3, bool, std::function)" << endl;
2323 }
2324
2325 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2326 RefCountedPtr<ItoSolver>& solver = solverIt();
2327 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2328
2329 const bool mobile = solver->isMobile();
2330 const bool diffusive = solver->isDiffusive();
2331 const bool charged = (species->getChargeNumber() != 0);
2332
2333 const EBIntersection intersectionAlgorithm = solver->getIntersectionAlgorithm();
2334
2335 switch (a_speciesSubset) {
2336 case SpeciesSubset::All: {
2337 solver->intersectParticles(a_containerBulk,
2338 a_containerEB,
2339 a_containerDomain,
2340 intersectionAlgorithm,
2341 a_delete,
2342 a_nonDeletionModifier);
2343
2344 break;
2345 }
2346 case SpeciesSubset::AllMobile: {
2347 if (mobile) {
2348 solver->intersectParticles(a_containerBulk,
2349 a_containerEB,
2350 a_containerDomain,
2351 intersectionAlgorithm,
2352 a_delete,
2353 a_nonDeletionModifier);
2354 }
2355
2356 break;
2357 }
2358 case SpeciesSubset::AllDiffusive: {
2359 if (diffusive) {
2360 solver->intersectParticles(a_containerBulk,
2361 a_containerEB,
2362 a_containerDomain,
2363 intersectionAlgorithm,
2364 a_delete,
2365 a_nonDeletionModifier);
2366 }
2367
2368 break;
2369 }
2370 case SpeciesSubset::AllMobileOrDiffusive: {
2371 if (mobile || diffusive) {
2372 solver->intersectParticles(a_containerBulk,
2373 a_containerEB,
2374 a_containerDomain,
2375 intersectionAlgorithm,
2376 a_delete,
2377 a_nonDeletionModifier);
2378 }
2379
2380 break;
2381 }
2382 case SpeciesSubset::AllMobileAndDiffusive: {
2383 if (mobile && diffusive) {
2384 solver->intersectParticles(a_containerBulk,
2385 a_containerEB,
2386 a_containerDomain,
2387 intersectionAlgorithm,
2388 a_delete,
2389 a_nonDeletionModifier);
2390 }
2391
2392 break;
2393 }
2394 case SpeciesSubset::Charged: {
2395 if (charged) {
2396 solver->intersectParticles(a_containerBulk,
2397 a_containerEB,
2398 a_containerDomain,
2399 intersectionAlgorithm,
2400 a_delete,
2401 a_nonDeletionModifier);
2402 }
2403
2404 break;
2405 }
2406 case SpeciesSubset::ChargedMobile: {
2407 if (charged && mobile) {
2408 solver->intersectParticles(a_containerBulk,
2409 a_containerEB,
2410 a_containerDomain,
2411 intersectionAlgorithm,
2412 a_delete,
2413 a_nonDeletionModifier);
2414 }
2415
2416 break;
2417 }
2418 case SpeciesSubset::ChargedDiffusive: {
2419 if (charged && diffusive) {
2420 solver->intersectParticles(a_containerBulk,
2421 a_containerEB,
2422 a_containerDomain,
2423 intersectionAlgorithm,
2424 a_delete,
2425 a_nonDeletionModifier);
2426 }
2427
2428 break;
2429 }
2430 case SpeciesSubset::ChargedMobileOrDiffusive: {
2431 if (charged && (mobile || diffusive)) {
2432 solver->intersectParticles(a_containerBulk,
2433 a_containerEB,
2434 a_containerDomain,
2435 intersectionAlgorithm,
2436 a_delete,
2437 a_nonDeletionModifier);
2438 }
2439
2440 break;
2441 }
2442 case SpeciesSubset::ChargedMobileAndDiffusive: {
2443 if (charged && (mobile && diffusive)) {
2444 solver->intersectParticles(a_containerBulk,
2445 a_containerEB,
2446 a_containerDomain,
2447 intersectionAlgorithm,
2448 a_delete,
2449 a_nonDeletionModifier);
2450 }
2451
2452 break;
2453 }
2454 case SpeciesSubset::Stationary: {
2455 if (!mobile && !diffusive) {
2456 solver->intersectParticles(a_containerBulk,
2457 a_containerEB,
2458 a_containerDomain,
2459 intersectionAlgorithm,
2460 a_delete,
2461 a_nonDeletionModifier);
2462 }
2463
2464 break;
2465 }
2466 default: {
2467 MayDay::Abort("ItoKMCStepper::intersectParticles - logic bust");
2468
2469 break;
2470 }
2471 }
2472 }
2473}
2474
2475template <typename I, typename C, typename R, typename F>
2476void
2478 const EBRepresentation a_representation,
2479 const Real a_tolerance) noexcept
2480{
2481 CH_TIME("ItoKMCStepper::removeCoveredParticles(SpeciesSubset, EBRepresentation, Real)");
2482 if (m_verbosity > 5) {
2483 pout() << m_name + "::removeCoveredParticles(SpeciesSubset, EBRepresentation, Real)" << endl;
2484 }
2485
2486 this->removeCoveredParticles(a_speciesSubset, ItoSolver::WhichContainer::Bulk, a_representation, a_tolerance);
2487}
2488
2489template <typename I, typename C, typename R, typename F>
2490void
2492 const ItoSolver::WhichContainer a_container,
2493 const EBRepresentation a_representation,
2494 const Real a_tolerance) noexcept
2495{
2496 CH_TIME("ItoKMCStepper::removeCoveredParticles(SpeciesSubset, container, EBRepresentation, tolerance)");
2497 if (m_verbosity > 5) {
2498 pout() << m_name + "::removeCoveredParticles(SpeciesSubset, container, EBRepresentation, tolerance)" << endl;
2499 }
2500
2501 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2502 RefCountedPtr<ItoSolver>& solver = solverIt();
2503 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2504
2505 const bool mobile = solver->isMobile();
2506 const bool diffusive = solver->isDiffusive();
2507 const bool charged = (species->getChargeNumber() != 0);
2508
2509 switch (a_which) {
2510 case SpeciesSubset::All: {
2511 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2512
2513 break;
2514 }
2515 case SpeciesSubset::AllMobile: {
2516 if (mobile) {
2517 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2518 }
2519
2520 break;
2521 }
2522 case SpeciesSubset::AllDiffusive: {
2523 if (diffusive) {
2524 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2525 }
2526
2527 break;
2528 }
2529 case SpeciesSubset::AllMobileOrDiffusive: {
2530 if (mobile || diffusive) {
2531 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2532 }
2533
2534 break;
2535 }
2536 case SpeciesSubset::AllMobileAndDiffusive: {
2537 if (mobile && diffusive) {
2538 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2539 }
2540
2541 break;
2542 }
2543 case SpeciesSubset::Charged: {
2544 if (charged) {
2545 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2546 }
2547
2548 break;
2549 }
2550 case SpeciesSubset::ChargedMobile: {
2551 if (charged && mobile) {
2552 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2553 }
2554
2555 break;
2556 }
2557 case SpeciesSubset::ChargedDiffusive: {
2558 if (charged && diffusive) {
2559 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2560 }
2561
2562 break;
2563 }
2564 case SpeciesSubset::ChargedMobileOrDiffusive: {
2565 if (charged && (mobile || diffusive)) {
2566 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2567 }
2568
2569 break;
2570 }
2571 case SpeciesSubset::ChargedMobileAndDiffusive: {
2572 if (charged && (mobile && diffusive)) {
2573 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2574 }
2575
2576 break;
2577 }
2578 case SpeciesSubset::Stationary: {
2579 if (!mobile && !diffusive) {
2580 solver->removeCoveredParticles(a_container, a_representation, a_tolerance);
2581 }
2582
2583 break;
2584 }
2585 default: {
2586 MayDay::Abort("ItoKMCStepper::removeCoveredParticles - logic bust");
2587
2588 break;
2589 }
2590 }
2591 }
2592}
2593
2594template <typename I, typename C, typename R, typename F>
2595void
2597 const EBRepresentation a_representation,
2598 const Real a_tolerance) noexcept
2599{
2600 CH_TIME("ItoKMCStepper::transferCoveredParticles(SpeciesSubset, EBRepresentation, Real)");
2601 if (m_verbosity > 5) {
2602 pout() << m_name + "::transferCoveredParticles(SpeciesSubset, EBRepresentation, Real)" << endl;
2603 }
2604
2605 this->transferCoveredParticles(a_speciesSubset,
2606 ItoSolver::WhichContainer::Bulk,
2607 ItoSolver::WhichContainer::Covered,
2608 a_representation,
2609 a_tolerance);
2610}
2611
2612template <typename I, typename C, typename R, typename F>
2613void
2615 const ItoSolver::WhichContainer a_containerFrom,
2616 const ItoSolver::WhichContainer a_containerTo,
2617 const EBRepresentation a_representation,
2618 const Real a_tolerance) noexcept
2619{
2620 CH_TIME("ItoKMCStepper::transferCoveredParticles(SpeciesSubset, Containerx2, EBRepresentation, Real)");
2621 if (m_verbosity > 5) {
2622 pout() << m_name + "::transferCoveredParticles(SpeciesSubset, Containerx2, EBRepresentation, Real)" << endl;
2623 }
2624
2625 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2626 RefCountedPtr<ItoSolver>& solver = solverIt();
2627 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2628
2629 const bool mobile = solver->isMobile();
2630 const bool diffusive = solver->isDiffusive();
2631 const bool charged = (species->getChargeNumber() != 0);
2632
2633 switch (a_speciesSubset) {
2634 case SpeciesSubset::All: {
2635 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2636
2637 break;
2638 }
2639 case SpeciesSubset::AllMobile: {
2640 if (mobile) {
2641 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2642 }
2643
2644 break;
2645 }
2646 case SpeciesSubset::AllDiffusive: {
2647 if (diffusive) {
2648 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2649 }
2650
2651 break;
2652 }
2653 case SpeciesSubset::AllMobileOrDiffusive: {
2654 if (mobile || diffusive) {
2655 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2656 }
2657
2658 break;
2659 }
2660 case SpeciesSubset::AllMobileAndDiffusive: {
2661 if (mobile && diffusive) {
2662 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2663 }
2664
2665 break;
2666 }
2667 case SpeciesSubset::Charged: {
2668 if (charged) {
2669 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2670 }
2671
2672 break;
2673 }
2674 case SpeciesSubset::ChargedMobile: {
2675 if (charged && mobile) {
2676 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2677 }
2678
2679 break;
2680 }
2681 case SpeciesSubset::ChargedDiffusive: {
2682 if (charged && diffusive) {
2683 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2684 }
2685
2686 break;
2687 }
2688 case SpeciesSubset::ChargedMobileOrDiffusive: {
2689 if (charged && (mobile || diffusive)) {
2690 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2691 }
2692
2693 break;
2694 }
2695 case SpeciesSubset::ChargedMobileAndDiffusive: {
2696 if (charged && (mobile && diffusive)) {
2697 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2698 }
2699
2700 break;
2701 }
2702 case SpeciesSubset::Stationary: {
2703 if (!mobile && !diffusive) {
2704 solver->transferCoveredParticles(a_containerFrom, a_containerTo, a_representation, a_tolerance);
2705 }
2706
2707 break;
2708 }
2709 default: {
2710 MayDay::Abort("ItoKMCStepper::transferCoveredParticles - logic bust");
2711
2712 break;
2713 }
2714 }
2715 }
2716}
2717
2718template <typename I, typename C, typename R, typename F>
2719void
2721{
2722 CH_TIME("ItoKMCStepper::remapParticles(SpeciesSubset)");
2723 if (m_verbosity > 5) {
2724 pout() << m_name + "::remapParticles(SpeciesSubset)" << endl;
2725 }
2726
2727 this->remapParticles(a_speciesSubset, ItoSolver::WhichContainer::Bulk);
2728}
2729
2730template <typename I, typename C, typename R, typename F>
2731void
2733 const ItoSolver::WhichContainer a_container) noexcept
2734{
2735 CH_TIME("ItoKMCStepper::remapParticles(SpeciesSubset, WhichContainer)");
2736 if (m_verbosity > 5) {
2737 pout() << m_name + "::remapParticles(SpeciesSubset, WhichContainer)" << endl;
2738 }
2739
2740 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2741 RefCountedPtr<ItoSolver>& solver = solverIt();
2742 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2743
2744 const bool mobile = solver->isMobile();
2745 const bool diffusive = solver->isDiffusive();
2746 const bool charged = (species->getChargeNumber() != 0);
2747
2748 switch (a_speciesSubset) {
2749 case SpeciesSubset::All: {
2750 solver->remap(a_container);
2751
2752 break;
2753 }
2754 case SpeciesSubset::AllMobile: {
2755 if (mobile) {
2756 solver->remap(a_container);
2757 }
2758
2759 break;
2760 }
2761 case SpeciesSubset::AllDiffusive: {
2762 if (diffusive) {
2763 solver->remap(a_container);
2764 }
2765
2766 break;
2767 }
2768 case SpeciesSubset::AllMobileOrDiffusive: {
2769 if (mobile || diffusive) {
2770 solver->remap(a_container);
2771 }
2772
2773 break;
2774 }
2775 case SpeciesSubset::AllMobileAndDiffusive: {
2776 if (mobile && diffusive) {
2777 solver->remap(a_container);
2778 }
2779
2780 break;
2781 }
2782 case SpeciesSubset::Charged: {
2783 if (charged) {
2784 solver->remap(a_container);
2785 }
2786
2787 break;
2788 }
2789 case SpeciesSubset::ChargedMobile: {
2790 if (charged && mobile) {
2791 solver->remap(a_container);
2792 }
2793
2794 break;
2795 }
2796 case SpeciesSubset::ChargedDiffusive: {
2797 if (charged && diffusive) {
2798 solver->remap(a_container);
2799 }
2800
2801 break;
2802 }
2803 case SpeciesSubset::ChargedMobileOrDiffusive: {
2804 if (charged && (mobile || diffusive)) {
2805 solver->remap(a_container);
2806 }
2807
2808 break;
2809 }
2810 case SpeciesSubset::ChargedMobileAndDiffusive: {
2811 if (charged && (mobile && diffusive)) {
2812 solver->remap(a_container);
2813 }
2814
2815 break;
2816 }
2817 case SpeciesSubset::Stationary: {
2818 if (!mobile && !diffusive) {
2819 solver->remap(a_container);
2820 }
2821
2822 break;
2823 }
2824 default: {
2825 MayDay::Abort("ItoKMCStepper::remapParticles - logic bust");
2826
2827 break;
2828 }
2829 }
2830 }
2831}
2832
2833template <typename I, typename C, typename R, typename F>
2834void
2836{
2837 CH_TIME("ItoKMCStepper::depositParticles(SpeciesSubset)");
2838 if (m_verbosity > 5) {
2839 pout() << m_name + "::depositParticles(SpeciesSubset)" << endl;
2840 }
2841
2842 this->depositParticles(a_speciesSubset, ItoSolver::WhichContainer::Bulk);
2843}
2844
2845template <typename I, typename C, typename R, typename F>
2846void
2848 const ItoSolver::WhichContainer a_container) noexcept
2849{
2850 CH_TIME("ItoKMCStepper::depositParticles(SpeciesSubset)");
2851 if (m_verbosity > 5) {
2852 pout() << m_name + "::depositParticles(SpeciesSubset)" << endl;
2853 }
2854
2855 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2856 RefCountedPtr<ItoSolver>& solver = solverIt();
2857 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2858
2859 const bool mobile = solver->isMobile();
2860 const bool diffusive = solver->isDiffusive();
2861 const bool charged = (species->getChargeNumber() != 0);
2862
2863 switch (a_speciesSubset) {
2864 case SpeciesSubset::All: {
2865 solver->depositParticles(a_container);
2866
2867 break;
2868 }
2869 case SpeciesSubset::AllMobile: {
2870 if (mobile) {
2871 solver->depositParticles(a_container);
2872 }
2873
2874 break;
2875 }
2876 case SpeciesSubset::AllDiffusive: {
2877 if (diffusive) {
2878 solver->depositParticles(a_container);
2879 }
2880
2881 break;
2882 }
2883 case SpeciesSubset::AllMobileOrDiffusive: {
2884 if (mobile || diffusive) {
2885 solver->depositParticles(a_container);
2886 }
2887
2888 break;
2889 }
2890 case SpeciesSubset::AllMobileAndDiffusive: {
2891 if (mobile && diffusive) {
2892 solver->depositParticles(a_container);
2893 }
2894
2895 break;
2896 }
2897 case SpeciesSubset::Charged: {
2898 if (charged) {
2899 solver->depositParticles(a_container);
2900 }
2901
2902 break;
2903 }
2904 case SpeciesSubset::ChargedMobile: {
2905 if (charged && mobile) {
2906 solver->depositParticles(a_container);
2907 }
2908
2909 break;
2910 }
2911 case SpeciesSubset::ChargedDiffusive: {
2912 if (charged && diffusive) {
2913 solver->depositParticles(a_container);
2914 }
2915
2916 break;
2917 }
2918 case SpeciesSubset::ChargedMobileOrDiffusive: {
2919 if (charged && (mobile || diffusive)) {
2920 solver->depositParticles(a_container);
2921 }
2922
2923 break;
2924 }
2925 case SpeciesSubset::ChargedMobileAndDiffusive: {
2926 if (charged && (mobile && diffusive)) {
2927 solver->depositParticles(a_container);
2928 }
2929
2930 break;
2931 }
2932 case SpeciesSubset::Stationary: {
2933 if (!mobile && !diffusive) {
2934 solver->depositParticles(a_container);
2935 }
2936
2937 break;
2938 }
2939 default: {
2940 MayDay::Abort("ItoKMCStepper::depositParticles - logic bust");
2941
2942 break;
2943 }
2944 }
2945 }
2946}
2947
2948template <typename I, typename C, typename R, typename F>
2949void
2951{
2952 CH_TIME("ItoKMCStepper::setItoVelocityFunctions");
2953 if (m_verbosity > 5) {
2954 pout() << m_name + "::setItoVelocityFunctions" << endl;
2955 }
2956
2957 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
2958 RefCountedPtr<ItoSolver>& solver = solverIt();
2959 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
2960 const int Z = species->getChargeNumber();
2961
2962 if (solver->isMobile() && Z != 0) {
2963 EBAMRCellData& velocityFunction = solver->getVelocityFunction();
2964 m_amr->copyData(velocityFunction, m_electricFieldParticle);
2965
2966 const int Z = species->getChargeNumber();
2967
2968 if (Z < 0) {
2969 DataOps::scale(velocityFunction, -1.0);
2970 }
2971
2972 // Coarsen and update ghost cells.
2973 m_amr->conservativeAverage(velocityFunction, m_particleRealm, m_plasmaPhase);
2974 m_amr->interpGhostPwl(velocityFunction, m_particleRealm, m_plasmaPhase);
2975 }
2976 }
2977}
2978
2979template <typename I, typename C, typename R, typename F>
2980void
2982{
2983 CH_TIME("ItoKMCStepper::setCdrVelocityFunctions");
2984 if (m_verbosity > 5) {
2985 pout() << m_name + "::setCdrVelocityFunctions" << endl;
2986 }
2987
2988 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
2989 RefCountedPtr<CdrSolver>& solver = solverIt();
2990 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
2991 const int Z = species->getChargeNumber();
2992
2993 if (solver->isMobile() && Z != 0) {
2994 EBAMRCellData& velocity = solver->getCellCenteredVelocity();
2995 m_amr->copyData(velocity, m_electricFieldFluid);
2996
2997 const int Z = species->getChargeNumber();
2998
2999 if (Z < 0) {
3000 DataOps::scale(velocity, -1.0);
3001 }
3002
3003 // Coarsen and update ghost cells.
3004 m_amr->conservativeAverage(velocity, m_fluidRealm, m_plasmaPhase);
3005 m_amr->interpGhostPwl(velocity, m_fluidRealm, m_plasmaPhase);
3006 }
3007 else if (solver->isMobile() && Z == 0) {
3008 MayDay::Warning("ItoKMCStepper::setCdrVelocityFunctions -- how to handle mobile neutral species?");
3009 }
3010 }
3011}
3012
3013template <typename I, typename C, typename R, typename F>
3014void
3016{
3017 CH_TIME("ItoKMCStepper::multiplyCdrVelocitiesByMobilities()");
3018 if (m_verbosity > 5) {
3019 pout() << m_name + "::multiplyCdrVelocitiesByMobilities()" << endl;
3020 }
3021
3022 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3023 RefCountedPtr<CdrSolver>& solver = solverIt();
3024 const int idx = solverIt.index();
3025
3026 if (solver->isMobile()) {
3027 EBAMRCellData& velocity = solver->getCellCenteredVelocity();
3028 const EBAMRCellData& mobility = m_cdrMobilities[idx];
3029
3030 DataOps::multiplyScalar(velocity, mobility);
3031
3032 // Coarsen and update ghost cells.
3033 m_amr->conservativeAverage(velocity, m_fluidRealm, m_plasmaPhase);
3034 m_amr->interpGhostPwl(velocity, m_fluidRealm, m_plasmaPhase);
3035 }
3036 }
3037}
3038
3039template <typename I, typename C, typename R, typename F>
3040void
3042{
3043 CH_TIME("ItoKMCStepper::computeDriftVelocities()");
3044 if (m_verbosity > 5) {
3045 pout() << m_name + "::computeDriftVelocities()" << endl;
3046 }
3047
3048 // Set velocities to be sgn(Z) * E
3049 this->setItoVelocityFunctions();
3050 this->setCdrVelocityFunctions();
3051
3052 // Compute mobilities for both Ito and CDR species.
3053 this->computeMobilities();
3054
3055 // Multiply sgn(Z) * E by the mobilities. For the CDR solvers this is just a multiplication, for the Ito solvers
3056 // we interpolate mu*E to the particle positions (in some form).
3057 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3058 solverIt()->interpolateVelocities();
3059 }
3060
3061 this->multiplyCdrVelocitiesByMobilities();
3062}
3063
3064template <typename I, typename C, typename R, typename F>
3065void
3067{
3068 CH_TIME("ItoKMCStepper::computeMobilities()");
3069 if (m_verbosity > 5) {
3070 pout() << m_name + "::computeMobilities()" << endl;
3071 }
3072
3073 Vector<EBAMRCellData*> itoMobilities = m_ito->getMobilityFunctions();
3074
3075 this->computeMobilities(itoMobilities, m_cdrMobilities, m_electricFieldFluid, m_time);
3076}
3077
3078template <typename I, typename C, typename R, typename F>
3079void
3080ItoKMCStepper<I, C, R, F>::computeMobilities(Vector<EBAMRCellData*>& a_itoMobilities,
3081 Vector<EBAMRCellData>& a_cdrMobilities,
3082 const EBAMRCellData& a_electricField,
3083 const Real a_time) noexcept
3084{
3085 CH_TIME("ItoKMCStepper::computeMobilities(mobilities, E, time)");
3086 if (m_verbosity > 5) {
3087 pout() << m_name + "::computeMobilities(mobilities, E, time)" << endl;
3088 }
3089
3090 const int numItoSpecies = m_physics->getNumItoSpecies();
3091 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3092
3093 CH_assert(a_electricField.getRealm() == m_fluidRealm);
3094 CH_assert(a_itoMobilities.size() == numItoSpecies);
3095 CH_assert(a_cdrMobilities.size() == numCdrSpecies);
3096
3097 // The mesh mobilities belong on the particle realm (they are the ItoSolver mobilities) but we need to run
3098 // the computation on the fluid realm. m_fluidScratchIto is that staging area -- a member rather than a
3099 // local, because allocating one hierarchy per species per time step defines an exchange Copier per level
3100 // for storage that does not outlive the call.
3101 CH_assert(m_fluidScratchIto.size() == numItoSpecies);
3102
3103 for (int i = 0; i < numItoSpecies; i++) {
3104 DataOps::setValue(m_fluidScratchIto[i], 0.0);
3105 DataOps::setValue(*a_itoMobilities[i], 0.0);
3106
3107 CH_assert(a_itoMobilities[i]->getRealm() == m_particleRealm);
3108 }
3109
3110 // Now run the computation on the fluid realm, computing the mobilities into fluidScratchMobilities
3111 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3112 Vector<LevelData<EBCellFAB>*> itoMobilities(numItoSpecies);
3113 Vector<LevelData<EBCellFAB>*> cdrMobilities(numCdrSpecies);
3114
3115 for (int i = 0; i < numItoSpecies; i++) {
3116 itoMobilities[i] = &(*(m_fluidScratchIto[i])[lvl]);
3117 }
3118
3119 for (int i = 0; i < numCdrSpecies; i++) {
3120 cdrMobilities[i] = &(*(a_cdrMobilities[i])[lvl]);
3121 }
3122
3123 // Run the level computation, which will fill the mobilities.
3124 this->computeMobilities(itoMobilities, cdrMobilities, *a_electricField[lvl], lvl, a_time);
3125 }
3126
3127 // Copy fluid realm data into particle realm and interpolate mobilities to the particle position.
3128 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3129 RefCountedPtr<ItoSolver>& solver = solverIt();
3130
3131 if (solver->isMobile()) {
3132 const int idx = solverIt.index();
3133
3134 m_amr->copyData(*a_itoMobilities[idx], m_fluidScratchIto[idx]);
3135 m_amr->conservativeAverage(*a_itoMobilities[idx], m_particleRealm, m_plasmaPhase);
3136 m_amr->interpGhostPwl(*a_itoMobilities[idx], m_particleRealm, m_plasmaPhase);
3137
3138 solver->interpolateMobilities();
3139 }
3140 }
3141}
3142
3143template <typename I, typename C, typename R, typename F>
3144void
3145ItoKMCStepper<I, C, R, F>::computeMobilities(Vector<LevelData<EBCellFAB>*>& a_itoMobilities,
3146 Vector<LevelData<EBCellFAB>*>& a_cdrMobilities,
3147 const LevelData<EBCellFAB>& a_electricField,
3148 const int a_level,
3149 const Real a_time) noexcept
3150{
3151 CH_TIME("ItoKMCStepper::computeMobilities(mobilities, E, level, time)");
3152 if (m_verbosity > 5) {
3153 pout() << m_name + "::computeMobilities(mobilities, E, level, time)" << endl;
3154 }
3155
3156 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[a_level];
3157 const DataIterator& dit = dbl.dataIterator();
3158
3159 const int nbox = dit.size();
3160
3161#pragma omp parallel for schedule(runtime)
3162 for (int mybox = 0; mybox < nbox; mybox++) {
3163 const DataIndex& din = dit[mybox];
3164
3165 const EBCellFAB& E = a_electricField[din];
3166 const Box cellBox = dbl[din];
3167
3168 Vector<EBCellFAB*> itoMobilities;
3169 Vector<EBCellFAB*> cdrMobilities;
3170
3171 for (int i = 0; i < a_itoMobilities.size(); i++) {
3172 itoMobilities.push_back(&((*a_itoMobilities[i])[din]));
3173 }
3174
3175 for (int i = 0; i < a_cdrMobilities.size(); i++) {
3176 cdrMobilities.push_back(&((*a_cdrMobilities[i])[din]));
3177 }
3178
3179 this->computeMobilities(itoMobilities, cdrMobilities, E, a_level, din, cellBox, a_time);
3180 }
3181}
3182
3183template <typename I, typename C, typename R, typename F>
3184void
3185ItoKMCStepper<I, C, R, F>::computeMobilities(Vector<EBCellFAB*>& a_itoMobilities,
3186 Vector<EBCellFAB*>& a_cdrMobilities,
3187 const EBCellFAB& a_electricField,
3188 const int a_level,
3189 const DataIndex a_din,
3190 const Box a_box,
3191 const Real a_time) noexcept
3192{
3193 CH_TIME("ItoKMCStepper::computeMobilities(meshMobilities, E, level, dit, box, time)");
3194 if (m_verbosity > 5) {
3195 pout() << m_name + "::computeMobilities(meshMobilities, E, level, dit, box, time)" << endl;
3196 }
3197
3198 // TLDR: We go through each and every cell and call the physics interface. This includes cells covered by a finer grid
3199 // but data is coarsened later anyways.
3200 const int numItoSpecies = m_physics->getNumItoSpecies();
3201 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3202 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
3203
3204 const Real dx = m_amr->getDx()[a_level];
3205 const RealVect probLo = m_amr->getProbLo();
3206 const EBISBox& ebisbox = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[a_level][a_din];
3207
3208 // Handles to regular data.
3209 const FArrayBox& electricFieldReg = a_electricField.getFArrayBox();
3210 Vector<FArrayBox*> itoMobilitiesReg(numItoSpecies);
3211 Vector<FArrayBox*> cdrMobilitiesReg(numCdrSpecies);
3212
3213 for (int i = 0; i < a_itoMobilities.size(); i++) {
3214 itoMobilitiesReg[i] = (&(a_itoMobilities[i]->getFArrayBox()));
3215 }
3216
3217 for (int i = 0; i < a_cdrMobilities.size(); i++) {
3218 cdrMobilitiesReg[i] = (&(a_cdrMobilities[i]->getFArrayBox()));
3219 }
3220
3221 // Physics interface mapping -- this maps a global index from the returned vector to an Ito or CDR solver
3222 const std::map<int, std::pair<SpeciesType, int>>& speciesMap = m_physics->getSpeciesMap();
3223
3224 // One buffer for the whole patch. The physics call fills it per cell; letting it return a vector instead
3225 // meant an allocation and a free in every cell of the domain. Declared here rather than in the kernels so
3226 // both share it -- they never run concurrently, and this function is already per-patch, so the buffer is
3227 // private to the thread that owns this patch.
3228 Vector<Real> mobilities;
3229
3230 // Regular kernel
3231 auto regularKernel = [&](const IntVect& iv) -> void {
3232 const RealVect pos = m_amr->getProbLo() + dx * (RealVect(iv) + 0.5 * RealVect::Unit);
3233 const RealVect E = RealVect(D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
3234
3235 // Call physics interface and compute mobilities for each species.
3236 m_physics->computeMobilities(mobilities, a_time, pos, E);
3237
3238 CH_assert(mobilities.size() == numPlasmaSpecies);
3239
3240 // Put the mobilities where they belong.
3241 for (const auto& s : speciesMap) {
3242 const int& globalIndex = s.first;
3243 const SpeciesType& type = s.second.first;
3244 const int& localIndex = s.second.second;
3245
3246 if (type == SpeciesType::Ito) {
3247 (*itoMobilitiesReg[localIndex])(iv, 0) = mobilities[globalIndex];
3248 }
3249 else if (type == SpeciesType::CDR) {
3250 (*cdrMobilitiesReg[localIndex])(iv, 0) = mobilities[globalIndex];
3251 }
3252 }
3253 };
3254
3255 // Irregular kernel.
3256 auto irregularKernel = [&](const VolIndex& vof) -> void {
3257 const RealVect e = RealVect(D_DECL(a_electricField(vof, 0), a_electricField(vof, 1), a_electricField(vof, 2)));
3258 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
3259
3260 // Call physics interface and compute mobilities for each species.
3261 m_physics->computeMobilities(mobilities, a_time, pos, e);
3262
3263 CH_assert(mobilities.size() == numPlasmaSpecies);
3264
3265 // Put the mobilities where they belong.
3266 for (const auto& s : speciesMap) {
3267 const int& globalIndex = s.first;
3268 const SpeciesType& type = s.second.first;
3269 const int& localIndex = s.second.second;
3270
3271 if (type == SpeciesType::Ito) {
3272 (*a_itoMobilities[localIndex])(vof, 0) = mobilities[globalIndex];
3273 }
3274 else if (type == SpeciesType::CDR) {
3275 (*a_cdrMobilities[localIndex])(vof, 0) = mobilities[globalIndex];
3276 }
3277 }
3278 };
3279
3280 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[a_level])[a_din];
3281
3282 // Run the kernels. Not vectorizable: m_physics->computeMobilities is an opaque virtual call
3283 // (heap allocation) per cell, then scattered to the per-species data holders.
3284 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
3285 BoxLoops::loop(vofit, irregularKernel);
3286
3287 // Covered is bogus.
3288 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3289 a_itoMobilities[solverIt.index()]->setCoveredCellVal(0.0, 0);
3290 }
3291
3292 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3293 a_cdrMobilities[solverIt.index()]->setCoveredCellVal(0.0, 0);
3294 }
3295}
3296
3297template <typename I, typename C, typename R, typename F>
3298void
3300{
3301 CH_TIME("ItoKMCStepper::computeDiffusionCoefficients()");
3302 if (m_verbosity > 5) {
3303 pout() << m_name + "::computeDiffusionCoefficients()" << endl;
3304 }
3305
3306 Vector<EBAMRCellData*> itoDiffusionCoefficients = m_ito->getDiffusionFunctions();
3307 Vector<EBAMRCellData*> cdrDiffusionCoefficients = m_cdr->getCellCenteredDiffusionCoefficients();
3308
3309 this->computeDiffusionCoefficients(itoDiffusionCoefficients, cdrDiffusionCoefficients, m_electricFieldFluid, m_time);
3310 this->averageDiffusionCoefficientsCellToFace();
3311}
3312
3313template <typename I, typename C, typename R, typename F>
3314void
3315ItoKMCStepper<I, C, R, F>::computeDiffusionCoefficients(Vector<EBAMRCellData*>& a_itoDiffusionCoefficients,
3316 Vector<EBAMRCellData*>& a_cdrDiffusionCoefficients,
3317 const EBAMRCellData& a_electricField,
3318 const Real a_time) noexcept
3319{
3320 CH_TIME("ItoKMCStepper::computeDiffusionCoefficients(Vector<EBAMRCellData*>, EBAMRCellData, Real)");
3321 if (m_verbosity > 5) {
3322 pout() << m_name + "::computeDiffusionCoefficients(Vector<EBAMRCellData*>, EBAMRCellData, Real)" << endl;
3323 }
3324
3325 const int numItoSpecies = m_physics->getNumItoSpecies();
3326 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3327
3328 CH_assert(a_electricField.getRealm() == m_fluidRealm);
3329 CH_assert(a_itoDiffusionCoefficients.size() == numItoSpecies);
3330 CH_assert(a_cdrDiffusionCoefficients.size() == numCdrSpecies);
3331
3332 // Sanity check -- things need to be defined on the correct realms.
3333 for (int i = 0; i < numItoSpecies; i++) {
3334 CH_assert(a_itoDiffusionCoefficients[i]->getRealm() == m_particleRealm);
3335 }
3336 for (int i = 0; i < numCdrSpecies; i++) {
3337 CH_assert(a_cdrDiffusionCoefficients[i]->getRealm() == m_fluidRealm);
3338 }
3339
3340 // The mesh diffusion coefficients belong on the particle realm (they are the ItoSolver diffusion
3341 // coefficients) but we need to run the computation on the fluid realm. m_fluidScratchIto is that staging
3342 // area; see computeMobilities(), which uses it for the same purpose at a different point in the step.
3343 CH_assert(m_fluidScratchIto.size() == numItoSpecies);
3344
3345 // Compute mesh-based diffusion coefficients on the fluid realm.
3346 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3347 Vector<LevelData<EBCellFAB>*> itoDiffusionCoefficients(numItoSpecies);
3348 Vector<LevelData<EBCellFAB>*> cdrDiffusionCoefficients(numCdrSpecies);
3349
3350 for (int i = 0; i < numItoSpecies; i++) {
3351 itoDiffusionCoefficients[i] = &(*(m_fluidScratchIto[i])[lvl]);
3352 }
3353 for (int i = 0; i < numCdrSpecies; i++) {
3354 cdrDiffusionCoefficients[i] = &(*(*a_cdrDiffusionCoefficients[i])[lvl]);
3355 }
3356
3357 this->computeDiffusionCoefficients(itoDiffusionCoefficients,
3358 cdrDiffusionCoefficients,
3359 *a_electricField[lvl],
3360 lvl,
3361 a_time);
3362 }
3363
3364 // Copy the fluid realm data over to the particle realm data and then coarsen and interpolate diffusion coefficients
3365 // to particle positions.
3366 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3367 RefCountedPtr<ItoSolver>& solver = solverIt();
3368
3369 if (solver->isDiffusive()) {
3370 const int idx = solverIt.index();
3371
3372 m_amr->copyData(*a_itoDiffusionCoefficients[idx], m_fluidScratchIto[idx]);
3373 m_amr->conservativeAverage(*a_itoDiffusionCoefficients[idx], m_particleRealm, m_plasmaPhase);
3374 m_amr->interpGhostPwl(*a_itoDiffusionCoefficients[idx], m_particleRealm, m_plasmaPhase);
3375
3376 solver->interpolateDiffusion();
3377 }
3378 }
3379}
3380
3381template <typename I, typename C, typename R, typename F>
3382void
3383ItoKMCStepper<I, C, R, F>::computeDiffusionCoefficients(Vector<LevelData<EBCellFAB>*>& a_itoDiffusionCoefficients,
3384 Vector<LevelData<EBCellFAB>*>& a_cdrDiffusionCoefficients,
3385 const LevelData<EBCellFAB>& a_electricField,
3386 const int a_level,
3387 const Real a_time) noexcept
3388{
3389 CH_TIME("ItoKMCStepper::computeDiffusionCoefficients(Vector<LD<EBCellFAB>*>, LD<EBCellFAB>, int, Real)");
3390 if (m_verbosity > 5) {
3391 pout() << m_name + "::computeDiffusionCoefficients(Vector<LD<EBCellFAB>*>, LD<EBCellFAB>, int, Real)" << endl;
3392 }
3393
3394 const int numItoSpecies = m_physics->getNumItoSpecies();
3395 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3396
3397 CH_assert(a_itoDiffusionCoefficients.size() == numItoSpecies);
3398 CH_assert(a_cdrDiffusionCoefficients.size() == numCdrSpecies);
3399
3400 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[a_level];
3401 const DataIterator& dit = dbl.dataIterator();
3402
3403 const int nbox = dit.size();
3404
3405#pragma omp parallel for schedule(runtime)
3406 for (int mybox = 0; mybox < nbox; mybox++) {
3407 const DataIndex& din = dit[mybox];
3408
3409 Vector<EBCellFAB*> itoDiffusionCoefficients(numItoSpecies);
3410 Vector<EBCellFAB*> cdrDiffusionCoefficients(numCdrSpecies);
3411
3412 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3413 const int idx = solverIt.index();
3414
3415 if (solverIt()->isDiffusive()) {
3416 itoDiffusionCoefficients[idx] = &(*a_itoDiffusionCoefficients[idx])[din];
3417 }
3418 else {
3419 itoDiffusionCoefficients[idx] = nullptr;
3420 }
3421 }
3422
3423 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3424 const int idx = solverIt.index();
3425
3426 if (solverIt()->isDiffusive()) {
3427 cdrDiffusionCoefficients[idx] = &(*a_cdrDiffusionCoefficients[idx])[din];
3428 }
3429 else {
3430 cdrDiffusionCoefficients[idx] = nullptr;
3431 }
3432 }
3433
3434 this->computeDiffusionCoefficients(itoDiffusionCoefficients,
3435 cdrDiffusionCoefficients,
3436 a_electricField[din],
3437 a_level,
3438 din,
3439 dbl[din],
3440 a_time);
3441 }
3442}
3443
3444template <typename I, typename C, typename R, typename F>
3445void
3446ItoKMCStepper<I, C, R, F>::computeDiffusionCoefficients(Vector<EBCellFAB*>& a_itoDiffusionCoefficients,
3447 Vector<EBCellFAB*>& a_cdrDiffusionCoefficients,
3448 const EBCellFAB& a_electricField,
3449 const int a_level,
3450 const DataIndex a_din,
3451 const Box a_box,
3452 const Real a_time) noexcept
3453{
3454 CH_TIME("ItoKMCStepper::computeDiffusionCoefficients(Patch)");
3455 if (m_verbosity > 5) {
3456 pout() << m_name + "::computeDiffusionCoefficients(Patch)" << endl;
3457 }
3458
3459 const int numItoSpecies = m_physics->getNumItoSpecies();
3460 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3461 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
3462
3463 CH_assert(a_electricField.nComp() == SpaceDim);
3464 CH_assert(a_itoDiffusionCoefficients.size() == numItoSpecies);
3465 CH_assert(a_cdrDiffusionCoefficients.size() == numCdrSpecies);
3466
3467 // Geometric information that we need.
3468 const Real dx = m_amr->getDx()[a_level];
3469 const RealVect probLo = m_amr->getProbLo();
3470 const EBISBox& ebisbox = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[a_level][a_din];
3471
3472 // Handle to single-valued data.
3473 const FArrayBox& electricFieldReg = a_electricField.getFArrayBox();
3474
3475 Vector<FArrayBox*> itoDiffCoReg(numItoSpecies, nullptr);
3476 Vector<FArrayBox*> cdrDiffCoReg(numCdrSpecies, nullptr);
3477
3478 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3479 RefCountedPtr<ItoSolver>& solver = solverIt();
3480
3481 if (solver->isDiffusive()) {
3482 const int i = solverIt.index();
3483 itoDiffCoReg[i] = &(a_itoDiffusionCoefficients[i]->getFArrayBox());
3484 }
3485 }
3486
3487 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3488 RefCountedPtr<CdrSolver>& solver = solverIt();
3489
3490 if (solver->isDiffusive()) {
3491 const int i = solverIt.index();
3492 cdrDiffCoReg[i] = &(a_cdrDiffusionCoefficients[i]->getFArrayBox());
3493 }
3494 }
3495
3496 // Physics interface mapping -- this maps a global index from the returned vector to an Ito or CDR solver
3497 const std::map<int, std::pair<SpeciesType, int>>& speciesMap = m_physics->getSpeciesMap();
3498
3499 // One buffer for the whole patch -- see computeMobilities() for why this is not left to the physics call.
3500 Vector<Real> diffusionCoefficients;
3501
3502 // Regular kernel definition.
3503 auto regularKernel = [&](const IntVect& iv) -> void {
3504 const RealVect pos = probLo + dx * (RealVect(iv) + 0.5 * RealVect::Unit);
3505 const RealVect E = RealVect(D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
3506
3507 // Compute diffusion coefficients.
3508 m_physics->computeDiffusionCoefficients(diffusionCoefficients, a_time, pos, E);
3509
3510 CH_assert(diffusionCoefficients.size() == numPlasmaSpecies);
3511
3512 // Put the diffusion coefficients in the correct solver storage.
3513 for (const auto& s : speciesMap) {
3514 const int& globalIndex = s.first;
3515 const SpeciesType& type = s.second.first;
3516 const int& localIndex = s.second.second;
3517
3518 // We need an explicit check to see if
3519 if (type == SpeciesType::Ito) {
3520 if (m_ito->getSolvers()[localIndex]->isDiffusive()) {
3521 (*itoDiffCoReg[localIndex])(iv, 0) = diffusionCoefficients[globalIndex];
3522 }
3523 }
3524 else if (type == SpeciesType::CDR) {
3525 if (m_cdr->getSolvers()[localIndex]->isDiffusive()) {
3526 (*cdrDiffCoReg[localIndex])(iv, 0) = diffusionCoefficients[globalIndex];
3527 }
3528 }
3529 }
3530 };
3531
3532 // Irregular kernel.
3533 auto irregularKernel = [&](const VolIndex& vof) -> void {
3534 const RealVect E = RealVect(D_DECL(a_electricField(vof, 0), a_electricField(vof, 1), a_electricField(vof, 2)));
3535 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
3536
3537 // Compute diffusion coefficients.
3538 m_physics->computeDiffusionCoefficients(diffusionCoefficients, a_time, pos, E);
3539
3540 // Put the diffusion coefficients in the correct solver storage.
3541 for (const auto& s : speciesMap) {
3542 const int& globalIndex = s.first;
3543 const SpeciesType& type = s.second.first;
3544 const int& localIndex = s.second.second;
3545
3546 // We need an explicit check to see if
3547 if (type == SpeciesType::Ito) {
3548 if (m_ito->getSolvers()[localIndex]->isDiffusive()) {
3549 (*a_itoDiffusionCoefficients[localIndex])(vof, 0) = diffusionCoefficients[globalIndex];
3550 }
3551 }
3552 else if (type == SpeciesType::CDR) {
3553 if (m_cdr->getSolvers()[localIndex]->isDiffusive()) {
3554 (*a_cdrDiffusionCoefficients[localIndex])(vof, 0) = diffusionCoefficients[globalIndex];
3555 }
3556 }
3557 }
3558 };
3559
3560 // Run kernels.
3561 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[a_level])[a_din];
3562
3563 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
3564 BoxLoops::loop(vofit, irregularKernel);
3565
3566 // Covered is bogus.
3567 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3568 if (solverIt()->isDiffusive()) {
3569 a_itoDiffusionCoefficients[solverIt.index()]->setCoveredCellVal(0.0, 0);
3570 }
3571 }
3572
3573 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3574 if (solverIt()->isDiffusive()) {
3575 a_cdrDiffusionCoefficients[solverIt.index()]->setCoveredCellVal(0.0, 0);
3576 }
3577 }
3578}
3579
3580template <typename I, typename C, typename R, typename F>
3581void
3583{
3584 CH_TIME("ItoKMCStepper::averageDiffusionCoefficientsCellToFace");
3585 if (m_verbosity > 5) {
3586 pout() << m_name + "::averageDiffusionCoefficientsCellToFace" << endl;
3587 }
3588
3589 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3590 RefCountedPtr<CdrSolver>& solver = solverIt();
3591
3592 if (solver->isDiffusive()) {
3593
3594 EBAMRCellData& cellCenteredDiffusionCoefficient = solver->getCellCenteredDiffusionCoefficient();
3595 EBAMRFluxData& faceCenteredDiffusionCoefficient = solver->getFaceCenteredDiffusionCoefficient();
3596
3597 CH_assert(cellCenteredDiffusionCoefficient.getRealm() == m_fluidRealm);
3598 CH_assert(faceCenteredDiffusionCoefficient.getRealm() == m_fluidRealm);
3599
3600 DataOps::setValue(faceCenteredDiffusionCoefficient, std::numeric_limits<Real>::max());
3601
3602 // Coarsen the cell-centered diffusion coefficient before averaging to faces.
3603 m_amr->arithmeticAverage(cellCenteredDiffusionCoefficient, m_fluidRealm, m_cdr->getPhase());
3604 m_amr->interpGhostPwl(cellCenteredDiffusionCoefficient, m_fluidRealm, m_cdr->getPhase());
3605
3606 // Average to cell faces. Note that this call also includes one ghost face (required when there's an EBCF
3607 // crossing).
3608 const int tanGhost = 1;
3609 const Interval interv = Interval(0, 0);
3610 const Average average = Average::Arithmetic;
3611
3612 DataOps::averageCellToFace(faceCenteredDiffusionCoefficient,
3613 cellCenteredDiffusionCoefficient,
3614 m_amr->getDomains(),
3615 tanGhost,
3616 interv,
3617 interv,
3618 average,
3619 m_amr->getFaceIteratorWithTangentialGhosts(m_fluidRealm, m_cdr->getPhase()));
3620 }
3621 }
3622}
3623
3624template <typename I, typename C, typename R, typename F>
3625void
3627{
3628 CH_TIME("ItoKMCStepper::getPhysicalParticlesPerCell(EBAMRCellData)");
3629 if (m_verbosity > 5) {
3630 pout() << m_name + "::getPhysicaParticlesPerCell(EBAMRCellData)" << endl;
3631 }
3632
3633 CH_assert(a_ppc.getRealm() == m_particleRealm);
3634
3635 for (auto it = m_ito->iterator(); it.ok(); ++it) {
3636 const int idx = it.index();
3637
3638 EBAMRCellData ppc = m_amr->slice(a_ppc, Interval(idx, idx));
3639
3640 const ParticleContainer<ItoParticle>& particles = it()->getParticles(ItoSolver::WhichContainer::Bulk);
3641
3643 }
3644}
3645
3646template <typename I, typename C, typename R, typename F>
3647void
3649{
3650 CH_TIME("ItoKMCStepper::computeReactiveItoParticlesPerCell(EBAMRCellData)");
3651 if (m_verbosity > 5) {
3652 pout() << m_name + "::computeReactiveItoParticlesPerCell(EBAMRCellData)" << endl;
3653 }
3654
3655 CH_assert(a_ppc.getRealm() == m_particleRealm);
3656
3657 DataOps::setValue(a_ppc, 0.0);
3658
3659 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3660 this->computeReactiveItoParticlesPerCell(*a_ppc[lvl], lvl);
3661 }
3662}
3663
3664template <typename I, typename C, typename R, typename F>
3665void
3666ItoKMCStepper<I, C, R, F>::computeReactiveItoParticlesPerCell(LevelData<EBCellFAB>& a_ppc, const int a_level) noexcept
3667{
3668 CH_TIME("ItoKMCStepper::computeReactiveItoParticlesPerCell(LD<EBCellFAB>, int)");
3669 if (m_verbosity > 5) {
3670 pout() << m_name + "::computeReactiveItoParticlesPerCell(LD<EBCellFAB>, int)" << endl;
3671 }
3672
3673 const int numItoSpecies = m_physics->getNumItoSpecies();
3674
3675 CH_assert(a_ppc.nComp() == numItoSpecies);
3676
3677 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[a_level];
3678 const EBISLayout& ebisl = m_amr->getEBISLayout(m_particleRealm, m_plasmaPhase)[a_level];
3679 const DataIterator& dit = dbl.dataIterator();
3680
3681 const int nbox = dit.size();
3682
3683#pragma omp parallel for schedule(runtime)
3684 for (int mybox = 0; mybox < nbox; mybox++) {
3685 const DataIndex& din = dit[mybox];
3686
3687 const Box box = dbl[din];
3688 const EBISBox& ebisbox = ebisl[din];
3689
3690 this->computeReactiveItoParticlesPerCell(a_ppc[din], a_level, din, box, ebisbox);
3691 }
3692}
3693
3694template <typename I, typename C, typename R, typename F>
3695void
3697 const int a_level,
3698 const DataIndex a_din,
3699 const Box a_box,
3700 const EBISBox& a_ebisbox) noexcept
3701{
3702 CH_TIME("ItoKMCStepper::computeReactiveItoParticlesPerCell(EBCellFAB, int, DataIndex, Box, EBISBox)");
3703 if (m_verbosity > 5) {
3704 pout() << m_name + "::computeReactiveItoParticlesPerCell(EBCellFAB, int, DataIndex, Box, EBISBox)" << endl;
3705 }
3706
3707 const int numItoSpecies = m_physics->getNumItoSpecies();
3708
3709 CH_assert(a_ppc.nComp() == numItoSpecies);
3710
3711 const Real dx = m_amr->getDx()[a_level];
3712 const RealVect probLo = m_amr->getProbLo();
3713
3714 // TLDR: We go through each solver and add the number of PHYSICAL particles per cell to a_ppc.
3715
3716 FArrayBox& ppcRegular = a_ppc.getFArrayBox();
3717
3718 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3719 RefCountedPtr<ItoSolver>& solver = solverIt();
3720 const int idx = solverIt.index();
3721
3722 ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
3723
3724 // Cell-sort the leaf and read each cell's particles straight out of its CSR range. This kernel only
3725 // sums weights, so there is nothing to extract into per-cell scratch containers.
3726 ParticleSoA<ItoParticle>& leaf = particles[a_level][a_din];
3727 leaf.sortByCell(a_box, dx * RealVect::Unit, probLo);
3728
3729 // Regular cells kernel.
3730 auto regularKernel = [&](const IntVect& iv) -> void {
3731 Real num = 0.0;
3732
3733 if (a_ebisbox.isRegular(iv)) {
3734 const std::pair<std::size_t, std::size_t> range = leaf.cellRange(a_box.index(iv));
3735 for (std::size_t i = range.first; i < range.second; i++) {
3736 num += leaf.weight(i);
3737 }
3738 }
3739
3740 ppcRegular(iv, idx) = num;
3741 };
3742
3743 // Irregular kernel -- note that only particles that lie inside the domain get to react.
3744 auto irregularKernel = [&](const VolIndex& vof) -> void {
3745 const IntVect iv = vof.gridIndex();
3746 const RealVect normal = a_ebisbox.normal(vof);
3747 const RealVect physCentroid = probLo + Location::position(Location::Cell::Boundary, vof, a_ebisbox, dx);
3748
3749 Real num = 0.0;
3750
3751 const std::pair<std::size_t, std::size_t> range = leaf.cellRange(a_box.index(iv));
3752 for (std::size_t i = range.first; i < range.second; i++) {
3753 const RealVect pos = leaf.position(i);
3754 if ((pos - physCentroid).dotProduct(normal) >= 0.0) {
3755 num += leaf.weight(i);
3756 }
3757 }
3758
3759 a_ppc(vof, idx) = num;
3760 };
3761
3762 // Run the kernels.
3763 VoFIterator& vofit = (*m_amr->getVofIterator(m_particleRealm, m_plasmaPhase)[a_level])[a_din];
3764
3765 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
3766 BoxLoops::loop(vofit, irregularKernel);
3767 }
3768}
3769
3770template <typename I, typename C, typename R, typename F>
3771void
3773{
3774 CH_TIME("ItoKMCStepper::computeReactiveCdrParticlesPerCell(EBAMRCellData)");
3775 if (m_verbosity > 5) {
3776 pout() << m_name + "::computeReactiveCdrParticlesPerCell(EBAMRCellData)" << endl;
3777 }
3778
3779 CH_assert(a_ppc.getRealm() == m_fluidRealm);
3780
3781 DataOps::setValue(a_ppc, 0.0);
3782
3783 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3784 this->computeReactiveCdrParticlesPerCell(*a_ppc[lvl], lvl);
3785 }
3786}
3787
3788template <typename I, typename C, typename R, typename F>
3789void
3790ItoKMCStepper<I, C, R, F>::computeReactiveCdrParticlesPerCell(LevelData<EBCellFAB>& a_ppc, const int a_level) noexcept
3791{
3792 CH_TIME("ItoKMCStepper::computeReactiveCdrParticlesPerCell(LD<EBCellFAB>, int)");
3793 if (m_verbosity > 5) {
3794 pout() << m_name + "::computeReactiveCdrParticlesPerCell(LD<EBCellFAB>, int)" << endl;
3795 }
3796
3797 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3798
3799 CH_assert(a_ppc.nComp() == numCdrSpecies);
3800
3801 if (numCdrSpecies > 0) {
3802 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[a_level];
3803 const EBISLayout& ebisl = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[a_level];
3804 const DataIterator& dit = dbl.dataIterator();
3805
3806 const int nbox = dit.size();
3807
3808#pragma omp parallel for schedule(runtime)
3809 for (int mybox = 0; mybox < nbox; mybox++) {
3810 const DataIndex& din = dit[mybox];
3811
3812 const Box box = dbl[din];
3813 const EBISBox& ebisbox = ebisl[din];
3814
3815 this->computeReactiveCdrParticlesPerCell(a_ppc[din], a_level, din, box, ebisbox);
3816 }
3817 }
3818}
3819
3820template <typename I, typename C, typename R, typename F>
3821void
3823 const int a_level,
3824 const DataIndex a_din,
3825 const Box a_box,
3826 const EBISBox& a_ebisbox) noexcept
3827{
3828 CH_TIME("ItoKMCStepper::computeReactiveCdrParticlesPerCell(EBCellFAB, int, DataIndex, Box, EBISBox)");
3829 if (m_verbosity > 5) {
3830 pout() << m_name + "::computeReactiveCdrParticlesPerCell(EBCellFAB, int, DataIndex, Box, EBISBox)" << endl;
3831 }
3832
3833 constexpr Real zero = 0.0;
3834
3835 const int numCdrSpecies = m_physics->getNumCdrSpecies();
3836
3837 CH_assert(a_ppc.nComp() == numCdrSpecies);
3838
3839 const Real dx = m_amr->getDx()[a_level];
3840 const Real vol = std::pow(dx, SpaceDim);
3841 const RealVect probLo = m_amr->getProbLo();
3842
3843 // TLDR: We go through each solver and add the number of PHYSICAL particles per cell to a_ppc.
3844 FArrayBox& ppcRegular = a_ppc.getFArrayBox();
3845
3846 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
3847 RefCountedPtr<CdrSolver>& solver = solverIt();
3848 const int idx = solverIt.index();
3849
3850 const EBCellFAB& phi = (*(solver->getPhi())[a_level])[a_din];
3851 const FArrayBox& phiReg = phi.getFArrayBox();
3852
3853 // Regular cells kernel. Not vectorizable: the out-of-line a_ebisbox.isRegular(iv) guard blocks it, and a
3854 // branchless mask-multiply rewrite still does not vectorize on GCC ("no vectype for stmt" on the
3855 // floor/strided-component store, verified via opt-record), so the original guarded form is kept.
3856 auto regularKernel = [&](const IntVect& iv) -> void {
3857 if (a_ebisbox.isRegular(iv)) {
3858 ppcRegular(iv, idx) = std::max(zero, std::floor(phiReg(iv, 0) * vol));
3859 }
3860 };
3861
3862 // Irregular kernel -- note that only particles that lie inside the domain get to react.
3863 auto irregularKernel = [&](const VolIndex& vof) -> void {
3864 const Real kappa = a_ebisbox.volFrac(vof);
3865
3866 a_ppc(vof, idx) = std::max(zero, std::floor(kappa * phi(vof, 0) * vol));
3867 };
3868
3869 // Run the kernels.
3870 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[a_level])[a_din];
3871
3872 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
3873 BoxLoops::loop(vofit, irregularKernel);
3874 }
3875}
3876
3877template <typename I, typename C, typename R, typename F>
3878void
3880{
3881 CH_TIME("ItoKMCStepper::computeReactiveMaeanEnergiesPerCell(EBAMRCellData)");
3882 if (m_verbosity > 5) {
3883 pout() << m_name + "::computeReactiveMaeanEnergiesPerCell(EBAMRCellData)" << endl;
3884 }
3885
3886 CH_assert(a_meanEnergies.getRealm() == m_particleRealm);
3887
3888 DataOps::setValue(a_meanEnergies, 0.0);
3889
3890 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3891 this->computeReactiveMeanEnergiesPerCell(*a_meanEnergies[lvl], lvl);
3892 }
3893}
3894
3895template <typename I, typename C, typename R, typename F>
3896void
3898 const int a_level) noexcept
3899{
3900 CH_TIME("ItoKMCStepper::computeReactiveMeanEnergiesPerCell(LD<EBCellFAB>, int)");
3901 if (m_verbosity > 5) {
3902 pout() << m_name + "::computeReactiveMeanEnergiesPerCell(LD<EBCellFAB>, int)" << endl;
3903 }
3904
3905 const int numPlasmaSpecies = m_physics->getNumItoSpecies();
3906
3907 CH_assert(a_meanEnergies.nComp() == numPlasmaSpecies);
3908
3909 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[a_level];
3910 const EBISLayout& ebisl = m_amr->getEBISLayout(m_particleRealm, m_plasmaPhase)[a_level];
3911 const DataIterator& dit = dbl.dataIterator();
3912
3913 const int nbox = dit.size();
3914
3915#pragma omp parallel for schedule(runtime)
3916 for (int mybox = 0; mybox < nbox; mybox++) {
3917 const DataIndex& din = dit[mybox];
3918
3919 const Box box = dbl[din];
3920 const EBISBox& ebisbox = ebisl[din];
3921
3922 this->computeReactiveMeanEnergiesPerCell(a_meanEnergies[din], a_level, din, box, ebisbox);
3923 }
3924}
3925
3926template <typename I, typename C, typename R, typename F>
3927void
3929 const int a_level,
3930 const DataIndex a_din,
3931 const Box a_box,
3932 const EBISBox& a_ebisbox) noexcept
3933{
3934 CH_TIME("ItoKMCStepper::computeReactiveMeanEnergiesPerCell(EBCellFABint, DataIndex, Box, EBISBox)");
3935 if (m_verbosity > 5) {
3936 pout() << m_name + "::computeReactiveMeanEnergiesPerCell(EBCellFABint, DataIndex, Box, EBISBox))" << endl;
3937 }
3938
3939 const int numPlasmaSpecies = m_physics->getNumItoSpecies();
3940
3941 CH_assert(a_meanEnergies.nComp() == numPlasmaSpecies);
3942
3943 const Real dx = m_amr->getDx()[a_level];
3944 const RealVect probLo = m_amr->getProbLo();
3945
3946 // Get single-valued data.
3947 FArrayBox& meanEnergiesReg = a_meanEnergies.getFArrayBox();
3948
3949 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
3950 RefCountedPtr<ItoSolver>& solver = solverIt();
3951 const int idx = solverIt.index();
3952
3953 ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
3954
3955 // Cell-sort the leaf and read each cell's particles straight out of its CSR range. This kernel only
3956 // accumulates weights and energies, so there is nothing to extract into per-cell scratch containers.
3957 ParticleSoA<ItoParticle>& leaf = particles[a_level][a_din];
3958 leaf.sortByCell(a_box, dx * RealVect::Unit, probLo);
3959
3960 // Regular grid cells.
3961 auto regularKernel = [&](const IntVect& iv) -> void {
3962 if (a_ebisbox.isRegular(iv)) {
3963 Real totalWeight = 0.0;
3964 Real totalEnergy = 0.0;
3965
3966 const std::pair<std::size_t, std::size_t> range = leaf.cellRange(a_box.index(iv));
3967 for (std::size_t i = range.first; i < range.second; i++) {
3968 const Real w = leaf.weight(i);
3969 totalWeight += w;
3970 totalEnergy += w * leaf.template get<&ItoParticle::energy>(i);
3971 }
3972
3973 if (totalWeight > 0.0) {
3974 meanEnergiesReg(iv, idx) = totalEnergy / totalWeight;
3975 }
3976 else {
3977 meanEnergiesReg(iv, idx) = 0.0;
3978 }
3979 }
3980 };
3981
3982 // Irregular cells -- note that only valid particles get to play with us.
3983 auto irregularKernel = [&](const VolIndex& vof) -> void {
3984 const IntVect iv = vof.gridIndex();
3985 const RealVect normal = a_ebisbox.normal(vof);
3986 const RealVect ebCentroid = probLo + Location::position(Location::Cell::Boundary, vof, a_ebisbox, dx);
3987
3988 Real totalWeight = 0.0;
3989 Real totalEnergy = 0.0;
3990
3991 const std::pair<std::size_t, std::size_t> range = leaf.cellRange(a_box.index(iv));
3992 for (std::size_t i = range.first; i < range.second; i++) {
3993 const RealVect pos = leaf.position(i);
3994
3995 if ((pos - ebCentroid).dotProduct(normal) >= 0.0) {
3996 const Real w = leaf.weight(i);
3997 totalWeight += w;
3998 totalEnergy += w * leaf.template get<&ItoParticle::energy>(i);
3999 }
4000 }
4001
4002 if (totalWeight > 0.0) {
4003 meanEnergiesReg(iv, idx) = totalEnergy / totalWeight;
4004 }
4005 else {
4006 meanEnergiesReg(iv, idx) = 0.0;
4007 }
4008 };
4009
4010 // Run the kernels.
4011 VoFIterator& vofit = (*m_amr->getVofIterator(m_particleRealm, m_plasmaPhase)[a_level])[a_din];
4012
4013 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
4014 BoxLoops::loop(vofit, irregularKernel);
4015 }
4016}
4017
4018template <typename I, typename C, typename R, typename F>
4019void
4021{
4022 CH_TIME("ItoKMCStepper::advanceReactionNetwork(dt)");
4023 if (m_verbosity > 5) {
4024 pout() << m_name + "::advanceReactionNetwork(dt)" << endl;
4025 }
4026
4027 CH_assert(a_dt > 0.0);
4028
4029 this->advanceReactionNetwork(m_electricFieldFluid, a_dt);
4030
4031 // NOTE: super-particle merging is NOT done here. Chemistry creates/removes particles; the caller
4032 // (e.g. ItoKMCGodunovStepper::advance()) merges afterwards through
4033 // ItoSolver::makeSuperparticles(), where it can be timed as its own step.
4034}
4035
4036template <typename I, typename C, typename R, typename F>
4037void
4038ItoKMCStepper<I, C, R, F>::advanceReactionNetwork(const EBAMRCellData& a_electricField, const Real a_dt) noexcept
4039{
4040 CH_TIMERS("ItoKMCStepper::advanceReactionNetwork");
4041 CH_TIMER("ItoKMCStepper::advanceReactionNetwork::compute_ppc", t1);
4042 CH_TIMER("ItoKMCStepper::advanceReactionNetwork::integrate_network", t2);
4043 CH_TIMER("ItoKMCStepper::advanceReactionNetwork::copies", t3);
4044 CH_TIMER("ItoKMCStepper::advanceReactionNetwork::reconcile_particles", t4);
4045 CH_TIMER("ItoKMCStepper::advanceReactionNetwork::reconcile_cdr", t5);
4046 if (m_verbosity > 5) {
4047 pout() << m_name + "::advanceReactionNetwork" << endl;
4048 }
4049
4050 const int numItoSpecies = m_physics->getNumItoSpecies();
4051 const int numCdrSpecies = m_physics->getNumCdrSpecies();
4052 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4053
4054 CH_assert(a_electricField.getRealm() == m_fluidRealm);
4055 CH_assert(a_dt > 0.0);
4056
4057 // Compute the number of reactive particles for both Ito and CDR species. Also do a backup of the initial number
4058 // of particles per cell. This is required when reconciling the results lateron.
4059 CH_START(t1);
4060 if (numItoSpecies > 0) {
4061 this->computeReactiveItoParticlesPerCell(m_particleItoPPC);
4062
4063 const Interval srcInterv(0, numItoSpecies - 1);
4064 const Interval dstInterv(0, numItoSpecies - 1);
4065
4066 m_amr->copyData(m_fluidPPC, m_particleItoPPC, dstInterv, srcInterv);
4067
4068 DataOps::copy(m_particleOldItoPPC, m_particleItoPPC);
4069 }
4070 if (numCdrSpecies > 0) {
4071 this->computeReactiveCdrParticlesPerCell(m_fluidCdrPPC);
4072
4073 const Interval srcInterv(0, numCdrSpecies - 1);
4074 const Interval dstInterv(numItoSpecies, numItoSpecies + numCdrSpecies - 1);
4075
4076 m_amr->copyData(m_fluidPPC, m_fluidCdrPPC, dstInterv, srcInterv);
4077
4078 DataOps::copy(m_fluidOldCdrPPC, m_fluidCdrPPC);
4079 }
4080
4081 DataOps::setValue(m_fluidYPC, 0.0);
4082 DataOps::setValue(m_particleYPC, 0.0);
4083 CH_STOP(t1);
4084
4085 // Advance the reaction network which gives us a new number of particles per cell, as well as the number of
4086 // photons that need to be generated per cell.
4087 CH_START(t2);
4088 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4089 this->advanceReactionNetwork(*m_fluidPPC[lvl], *m_fluidYPC[lvl], *a_electricField[lvl], lvl, a_dt);
4090 }
4091 CH_STOP(t2);
4092
4093 // Copy the results back to the holders that hold the number of particles per cell for Ito/Cdr solvers.
4094 CH_START(t3);
4095 if (numItoSpecies > 0) {
4096 const Interval srcInterv(0, numItoSpecies - 1);
4097 const Interval dstInterv(0, numItoSpecies - 1);
4098
4099 m_amr->copyData(m_particleItoPPC, m_fluidPPC, dstInterv, srcInterv);
4100 }
4101 if (numCdrSpecies > 0) {
4102 const Interval srcInterv(numItoSpecies, numItoSpecies + numCdrSpecies - 1);
4103 const Interval dstInterv(0, numCdrSpecies - 1);
4104
4105 m_amr->copyData(m_fluidCdrPPC, m_fluidPPC, dstInterv, srcInterv);
4106 }
4107 if (numPhotonSpecies > 0) {
4108 m_amr->copyData(m_particleYPC, m_fluidYPC);
4109 }
4110 CH_STOP(t3);
4111
4112 // Turn the per-cell CDR result into the quantities the reconciliation below needs. m_fluidCdrPPC holds the new
4113 // count and m_fluidOldCdrPPC the old one, so their difference is the change the network computed; both holders
4114 // are dead afterwards and are reused to carry the result.
4115 //
4116 // Under CdrProductInjection::Particle the change is split by sign, and only the PRODUCTION becomes particles:
4117 // that is the half of the change that happens at a point in the cell and therefore has a sub-cell position to be
4118 // consistent about. Mass removed from a CDR species is proportional to the cell-averaged density, which carries
4119 // no sub-cell information, so it stays in the cell it was taken from. m_fluidCdrPPC then holds the removal (and,
4120 // after the deposition below, the total change) and m_fluidOldCdrPPC the production.
4121 //
4122 // Under CdrProductInjection::Mesh there is nothing to split -- the whole change goes into the cell it was
4123 // computed in, and m_fluidCdrPPC carries it straight to reconcileCdrDensities().
4124 CH_START(t4);
4125 if (numCdrSpecies > 0) {
4126 DataOps::incr(m_fluidCdrPPC, m_fluidOldCdrPPC, -1.0);
4127
4128 if (m_cdrProductInjection == CdrProductInjection::Particle) {
4129 const Vector<RefCountedPtr<LayoutData<VoFIterator>>>& vofIter = m_amr->getVofIterator(m_fluidRealm,
4130 m_plasmaPhase);
4131
4132 DataOps::copy(m_fluidOldCdrPPC, m_fluidCdrPPC);
4133
4134 DataOps::floor(m_fluidOldCdrPPC, 0.0, vofIter);
4135 DataOps::roof(m_fluidCdrPPC, 0.0, vofIter);
4136
4137 m_amr->copyData(m_particleCdrProduction, m_fluidOldCdrPPC);
4138 }
4139 }
4140
4141 for (int i = 0; i < m_cdrProducts.size(); i++) {
4142 m_cdrProducts[i]->clearParticles();
4143 m_cdrProducts[i]->organizeParticlesByCell();
4144 }
4145
4146 // Reconcile the results -- for the discrete solvers we add/remove particles/photons, and the CDR production is
4147 // turned into the particles that the deposition below puts on the mesh.
4148 this->reconcileParticles(m_particleItoPPC,
4149 m_particleOldItoPPC,
4150 m_particleYPC,
4151 m_particleCdrProduction,
4152 m_electricFieldParticle);
4153 CH_STOP(t4);
4154
4155 // Deposit the CDR products and add them to the removal already in m_fluidCdrPPC, giving the total per-cell change.
4156 CH_START(t5);
4157 this->depositCdrProducts(m_fluidCdrPPC);
4158
4159 this->reconcileCdrDensities(m_fluidCdrPPC, a_dt);
4160 CH_STOP(t5);
4161}
4162
4163template <typename I, typename C, typename R, typename F>
4164void
4165ItoKMCStepper<I, C, R, F>::depositCdrProducts(EBAMRCellData& a_cdrChange) noexcept
4166{
4167 CH_TIME("ItoKMCStepper::depositCdrProducts");
4168 if (m_verbosity > 5) {
4169 pout() << m_name + "::depositCdrProducts" << endl;
4170 }
4171
4172 CH_assert(a_cdrChange.getRealm() == m_fluidRealm);
4173
4174 // Species whose product container is empty on EVERY rank deposit identically zero, so the whole block
4175 // below -- a halo deposition (which alone is ~3L-1 synchronizing stages), a cross-realm copy, and two
4176 // hierarchy-wide passes -- adds nothing to a_cdrChange.
4177 //
4178 // The test must be global because the work being skipped is collective: every rank has to reach the
4179 // same verdict for every species. One Allreduce over the whole vector answers it for all of them, and
4180 // the local counts it reduces are free (the containers track their own size). Skipping leaves stale
4181 // data in m_particleScratch1/m_fluidScratch1, which is harmless -- both are fully overwritten by their
4182 // next user.
4183 Vector<long long int> numProducts(m_cdrProducts.size(), 0LL);
4184
4185 for (int i = 0; i < m_cdrProducts.size(); i++) {
4186 numProducts[i] = static_cast<long long int>(m_cdrProducts[i]->getNumberOfValidParticlesLocal());
4187 }
4188
4189 ParallelOps::sum(numProducts);
4190
4191 // Under CdrProductInjection::Particle the products go down through ItoSolver::depositWeight, so they get the Ito
4192 // solvers' deposition in full -- kernel, coarse-fine strategy, cut-cell strategy and the mirror/redistribution
4193 // passes that go with it. That is the whole point: the two halves of a reaction then spread over the mesh the same
4194 // way, cut cells included. Any solver will do, since they all parse the same 'ItoSolver.deposition*' keys.
4195 //
4196 // Taking the cut-cell strategy from the solver is also why reconcileCdrDensities() skips the reactive
4197 // redistribution in this mode. The in-cell path is written in the kappa*n convention -- computeReactive-
4198 // CdrParticlesPerCell() reads a cut cell as kappa*phi*V and reconcileCdrDensities() writes it back as
4199 // phi += change/V, so a cut-cell reaction is damped by kappa and 'redistribute_cdr' exists to push the
4200 // resulting (1-kappa) share back out. A production deposited by the Ito solver carries the solver's cut-cell
4201 // normalization instead, so there is no such damping left for the redistribution to correct.
4202 //
4203 // Under CdrProductInjection::Mesh the only particles that reach here are the photoionization products, which have
4204 // no in-cell representation to fall back on. They are put down NGP with no cut-cell treatment, which is what the
4205 // code did before the chemistry products could become particles.
4206 const bool useItoDeposition = (m_cdrProductInjection == CdrProductInjection::Particle) &&
4207 (m_physics->getNumItoSpecies() > 0);
4208
4209 for (int i = 0; i < m_cdrProducts.size(); i++) {
4210 if (numProducts[i] == 0LL) {
4211 continue;
4212 }
4213
4214 m_cdrProducts[i]->organizeParticlesByPatch();
4215
4216 if (useItoDeposition) {
4217 const auto& solver = m_ito->getSolvers()[0];
4218
4219 solver->depositWeight(m_particleScratch1,
4220 *m_cdrProducts[i],
4221 solver->getDeposition(),
4222 solver->getCoarseFineDeposition());
4223 }
4224 else {
4225 m_amr->depositWeight(m_particleScratch1,
4226 m_particleRealm,
4227 m_plasmaPhase,
4228 *m_cdrProducts[i],
4229 DepositionType::NGP,
4230 CoarseFineDeposition::Halo,
4232 }
4233
4234 m_amr->copyData(m_fluidScratch1, m_particleScratch1);
4235 DataOps::volumeScale(m_fluidScratch1, m_amr->getDx());
4236
4237 EBAMRCellData cdrChange = m_amr->slice(a_cdrChange, Interval(i, i));
4238
4239 DataOps::incr(cdrChange, m_fluidScratch1, 1.0);
4240
4241 m_cdrProducts[i]->clearParticles();
4242 }
4243}
4244
4245template <typename I, typename C, typename R, typename F>
4246inline void
4247ItoKMCStepper<I, C, R, F>::advanceReactionNetwork(LevelData<EBCellFAB>& a_particlesPerCell,
4248 LevelData<EBCellFAB>& a_newPhotonsPerCell,
4249 const LevelData<EBCellFAB>& a_electricField,
4250 const int a_level,
4251 const Real a_dt) const noexcept
4252{
4253 CH_TIME("ItoKMCStepper::advanceReactionNetwork(LD<EBCellFAB>x3, int, Real)");
4254 if (m_verbosity > 5) {
4255 pout() << m_name + "::advanceReactionNetwork(LD<EBCellFAB>x3, int, Real)" << endl;
4256 }
4257
4258 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
4259 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4260
4261 CH_assert(a_particlesPerCell.nComp() == numPlasmaSpecies);
4262 CH_assert(a_newPhotonsPerCell.nComp() == numPhotonSpecies);
4263 CH_assert(a_electricField.nComp() == SpaceDim);
4264
4265 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[a_level];
4266 const DataIterator& dit = dbl.dataIterator();
4267
4268 const int nbox = dit.size();
4269
4270#pragma omp parallel
4271 {
4272 m_physics->defineKMC();
4273
4274#pragma omp for schedule(runtime)
4275 for (int mybox = 0; mybox < nbox; mybox++) {
4276 const DataIndex& din = dit[mybox];
4277
4278 this->advanceReactionNetwork(a_particlesPerCell[din],
4279 a_newPhotonsPerCell[din],
4280 a_electricField[din],
4281 a_level,
4282 din,
4283 dbl[din],
4284 m_amr->getDx()[a_level],
4285 a_dt);
4286 }
4287
4288 m_physics->killKMC();
4289 }
4290}
4291
4292template <typename I, typename C, typename R, typename F>
4293inline void
4295 EBCellFAB& a_newPhotonsPerCell,
4296 const EBCellFAB& a_electricField,
4297 const int a_level,
4298 const DataIndex a_din,
4299 const Box a_box,
4300 const Real a_dx,
4301 const Real a_dt) const noexcept
4302{
4303 CH_TIME("ItoKMCStepper::advanceReactionNetwork(EBCellFABx3, int, DataIndex, Box, Realx2)");
4304 if (m_verbosity > 5) {
4305 pout() << m_name + "::advanceReactionNetwork(EBCellFABx3, int, DataIndex, Box, Realx2)" << endl;
4306 }
4307
4308 const int numCdrSpecies = m_physics->getNumCdrSpecies();
4309 const int numItoSpecies = m_physics->getNumItoSpecies();
4310 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
4311 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4312
4313 CH_assert(a_particlesPerCell.nComp() == numPlasmaSpecies);
4314 CH_assert(a_newPhotonsPerCell.nComp() == numPhotonSpecies);
4315 CH_assert(a_electricField.nComp() == SpaceDim);
4316
4317 // Geometric information that we require.
4318 const RealVect probLo = m_amr->getProbLo();
4319 const EBISBox& ebisbox = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[a_level][a_din];
4320
4321 const FArrayBox& electricFieldReg = a_electricField.getFArrayBox();
4322
4323 // Storage used by physics interface.
4324 Vector<Physics::ItoKMC::FPR> particles(numPlasmaSpecies);
4325 Vector<Physics::ItoKMC::FPR> newPhotons(numPhotonSpecies);
4326 Vector<Real> meanEnergies(numPlasmaSpecies);
4327 Vector<Real> energySources(numPlasmaSpecies);
4328 Vector<Real> densities(numPlasmaSpecies, 0.0);
4329 Vector<RealVect> densityGradients(numPlasmaSpecies, RealVect::Zero);
4330
4331 // Populate single-valued data.
4332 FArrayBox& particlesPerCellReg = a_particlesPerCell.getFArrayBox();
4333 FArrayBox& newPhotonsReg = a_newPhotonsPerCell.getFArrayBox();
4334
4335 // Handle to densities and density gradients for CDR and Ito species.
4336 Vector<const EBCellFAB*> densitiesIto(numItoSpecies);
4337 Vector<const EBCellFAB*> densityGradientsIto(numItoSpecies);
4338 Vector<const FArrayBox*> densitiesItoReg(numItoSpecies);
4339 Vector<const FArrayBox*> densityGradientsItoReg(numItoSpecies);
4340
4341 Vector<const EBCellFAB*> densitiesCDR(numCdrSpecies);
4342 Vector<const EBCellFAB*> densityGradientsCDR(numCdrSpecies);
4343 Vector<const FArrayBox*> densitiesCDRReg(numCdrSpecies);
4344 Vector<const FArrayBox*> densityGradientsCDRReg(numCdrSpecies);
4345
4346 // Handle to critical and non-critical time step containers.
4347 EBCellFAB& physicsDt = (*m_kmcDt[a_level])[a_din];
4348
4349 FArrayBox& physicsDtReg = physicsDt.getFArrayBox();
4350
4351 physicsDt.setVal(std::numeric_limits<Real>::max());
4352
4353 for (auto it = m_ito->iterator(); it.ok(); ++it) {
4354 const RefCountedPtr<ItoSolver>& solver = it();
4355
4356 const int i = it.index();
4357
4358 densitiesIto[i] = &(*(m_fluidPhiIto[i])[a_level])[a_din];
4359 densitiesItoReg[i] = &(densitiesIto[i]->getFArrayBox());
4360 densityGradientsIto[i] = &(*m_fluidGradPhiIto[i][a_level])[a_din];
4361 densityGradientsItoReg[i] = &(densityGradientsIto[i]->getFArrayBox());
4362 }
4363
4364 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
4365 const RefCountedPtr<CdrSolver>& solver = it();
4366 const EBAMRCellData& phi = solver->getPhi();
4367
4368 const int i = it.index();
4369
4370 densitiesCDR[i] = &(*phi[a_level])[a_din];
4371 densitiesCDRReg[i] = &(densitiesCDR[i]->getFArrayBox());
4372 densityGradientsCDR[i] = &(*m_fluidGradPhiCDR[i][a_level])[a_din];
4373 densityGradientsCDRReg[i] = &(densityGradientsCDR[i]->getFArrayBox());
4374 }
4375
4376 // Handle to valid grid cells.
4377 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_fluidRealm)[a_level])[a_din];
4378
4379 // Regular cells
4380 auto regularKernel = [&](const IntVect& iv) -> void {
4381 if (ebisbox.isRegular(iv) && validCells(iv, 0)) {
4382 const RealVect pos = probLo + a_dx * (RealVect(iv) + 0.5 * RealVect::Unit);
4383 const RealVect E = RealVect(D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
4384
4385 // Populate the data holders that the physics interface requires.
4386 for (int i = 0; i < numPlasmaSpecies; i++) {
4387 particles[i] = llround(particlesPerCellReg(iv, i));
4388 }
4389
4390 for (int i = 0; i < numPhotonSpecies; i++) {
4391 newPhotons[i] = 0LL;
4392 }
4393
4394 // Populate gradients.
4395 for (int i = 0; i < numItoSpecies; i++) {
4396 densities[i] = (*densitiesItoReg[i])(iv, 0);
4397 densityGradients[i] = RealVect(D_DECL((*densityGradientsItoReg[i])(iv, 0),
4398 (*densityGradientsItoReg[i])(iv, 1),
4399 (*densityGradientsItoReg[i])(iv, 2)));
4400 }
4401
4402 for (int i = 0; i < numCdrSpecies; i++) {
4403 densities[numItoSpecies + i] = (*densitiesCDRReg[i])(iv, 0);
4404 densityGradients[numItoSpecies + i] = RealVect(D_DECL((*densityGradientsCDRReg[i])(iv, 0),
4405 (*densityGradientsCDRReg[i])(iv, 1),
4406 (*densityGradientsCDRReg[i])(iv, 2)));
4407 }
4408
4409 // Do the physics advance.
4410 Real physDt = std::numeric_limits<Real>::max();
4411
4412 m_physics->advanceKMC(particles, newPhotons, physDt, densities, densityGradients, a_dt, E, pos, a_dx, 1.0);
4413
4414 // Repopulate the input data holders with the new number of particles/photons per cell.
4415 for (int i = 0; i < numPlasmaSpecies; i++) {
4416 particlesPerCellReg(iv, i) = 1.0 * particles[i];
4417 }
4418
4419 for (int i = 0; i < numPhotonSpecies; i++) {
4420 newPhotonsReg(iv, i) = 1.0 * newPhotons[i];
4421 }
4422
4423 physicsDtReg(iv, 0) = physDt;
4424 }
4425 };
4426
4427 // Irregular cells
4428 auto irregularKernel = [&](const VolIndex& vof) -> void {
4429 const IntVect iv = vof.gridIndex();
4430
4431 if (ebisbox.isIrregular(iv) && validCells(iv, 0)) {
4432 const Real kappa = ebisbox.volFrac(vof);
4433 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, a_dx);
4434 const RealVect E = RealVect(D_DECL(a_electricField(vof, 0), a_electricField(vof, 1), a_electricField(vof, 2)));
4435
4436 // Initialize for this cell.
4437 for (int i = 0; i < numPlasmaSpecies; i++) {
4438 particles[i] = llround(a_particlesPerCell(vof, i));
4439 }
4440
4441 for (int i = 0; i < numPhotonSpecies; i++) {
4442 newPhotons[i] = 0LL;
4443 }
4444
4445 // Populate gradients.
4446 for (int i = 0; i < numItoSpecies; i++) {
4447 densities[i] = (*densitiesIto[i])(vof, 0);
4448 densityGradients[i] = RealVect(D_DECL((*densityGradientsIto[i])(vof, 0),
4449 (*densityGradientsIto[i])(vof, 1),
4450 (*densityGradientsIto[i])(vof, 2)));
4451 }
4452
4453 for (int i = 0; i < numCdrSpecies; i++) {
4454 densities[numItoSpecies + i] = (*densitiesCDR[i])(vof, 0);
4455 densityGradients[numItoSpecies + i] = RealVect(D_DECL((*densityGradientsCDR[i])(vof, 0),
4456 (*densityGradientsCDR[i])(vof, 1),
4457 (*densityGradientsCDR[i])(vof, 2)));
4458 }
4459
4460 // Do the physics advance
4461 Real physDt = std::numeric_limits<Real>::max();
4462
4463 m_physics->advanceKMC(particles, newPhotons, physDt, densities, densityGradients, a_dt, E, pos, a_dx, kappa);
4464
4465 // Repopulate the input data holders with the new number of particles/photons per cell.
4466 for (int i = 0; i < numPlasmaSpecies; i++) {
4467 a_particlesPerCell(vof, i) = 1.0 * particles[i];
4468 }
4469
4470 for (int i = 0; i < numPhotonSpecies; i++) {
4471 a_newPhotonsPerCell(vof, i) = 1.0 * newPhotons[i];
4472 }
4473
4474 physicsDt(vof, 0) = physDt;
4475 }
4476 };
4477
4478 // Run the kernels. Not vectorizable: the core m_physics->advanceKMC stochastic reaction advance is a
4479 // virtual call per cell with per-cell std::vector state -- inherently scalar (this is the hot reaction path
4480 // but cannot vectorize).
4481 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[a_level])[a_din];
4482
4483 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
4484 BoxLoops::loop(vofit, irregularKernel);
4485}
4486
4487template <typename I, typename C, typename R, typename F>
4488inline void
4489ItoKMCStepper<I, C, R, F>::reconcileParticles(const EBAMRCellData& a_newParticlesPerCell,
4490 const EBAMRCellData& a_oldParticlesPerCell,
4491 const EBAMRCellData& a_newPhotonsPerCell,
4492 const EBAMRCellData& a_cdrProduction,
4493 const EBAMRCellData& a_electricField) const noexcept
4494{
4495 CH_TIME("ItoKMCStepper::reconcileParticles(EBAMRCellDatax4)");
4496 if (m_verbosity > 5) {
4497 pout() << m_name + "::reconcileParticles(EBAMRCellDatax4)";
4498 }
4499
4500 CH_assert(a_newParticlesPerCell.getRealm() == m_particleRealm);
4501 CH_assert(a_oldParticlesPerCell.getRealm() == m_particleRealm);
4502 CH_assert(a_newPhotonsPerCell.getRealm() == m_particleRealm);
4503 CH_assert(a_cdrProduction.getRealm() == m_particleRealm);
4504 CH_assert(a_electricField.getRealm() == m_particleRealm);
4505
4506 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4507 this->reconcileParticles(*a_newParticlesPerCell[lvl],
4508 *a_oldParticlesPerCell[lvl],
4509 *a_newPhotonsPerCell[lvl],
4510 *a_cdrProduction[lvl],
4511 *a_electricField[lvl],
4512 lvl);
4513 }
4514}
4515
4516template <typename I, typename C, typename R, typename F>
4517inline void
4518ItoKMCStepper<I, C, R, F>::reconcileParticles(const LevelData<EBCellFAB>& a_newParticlesPerCell,
4519 const LevelData<EBCellFAB>& a_oldParticlesPerCell,
4520 const LevelData<EBCellFAB>& a_newPhotonsPerCell,
4521 const LevelData<EBCellFAB>& a_cdrProduction,
4522 const LevelData<EBCellFAB>& a_electricField,
4523 const int a_level) const noexcept
4524{
4525 CH_TIME("ItoKMCStepper::reconcileParticles(LevelData<EBCellFAB>x4, int)");
4526 if (m_verbosity > 5) {
4527 pout() << m_name + "::reconcileParticles(LevelData<EBCellFAB>x4, int)" << endl;
4528 }
4529
4530 const int numItoSpecies = m_physics->getNumItoSpecies();
4531 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4532
4533 CH_assert(a_newParticlesPerCell.nComp() == numItoSpecies);
4534 CH_assert(a_oldParticlesPerCell.nComp() == numItoSpecies);
4535 CH_assert(a_newPhotonsPerCell.nComp() == numPhotonSpecies);
4536 CH_assert(a_electricField.nComp() == SpaceDim);
4537
4538 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[a_level];
4539 const DataIterator& dit = dbl.dataIterator();
4540
4541 const int nbox = dit.size();
4542
4543#pragma omp parallel for schedule(runtime)
4544 for (int mybox = 0; mybox < nbox; mybox++) {
4545 const DataIndex& din = dit[mybox];
4546
4547 this->reconcileParticles(a_newParticlesPerCell[din],
4548 a_oldParticlesPerCell[din],
4549 a_newPhotonsPerCell[din],
4550 a_cdrProduction[din],
4551 a_electricField[din],
4552 a_level,
4553 din,
4554 dbl[din],
4555 m_amr->getDx()[a_level]);
4556 }
4557}
4558
4559template <typename I, typename C, typename R, typename F>
4560inline void
4561ItoKMCStepper<I, C, R, F>::reconcileParticles(const EBCellFAB& a_newParticlesPerCell,
4562 const EBCellFAB& a_oldParticlesPerCell,
4563 const EBCellFAB& a_newPhotonsPerCell,
4564 const EBCellFAB& a_cdrProduction,
4565 const EBCellFAB& a_electricField,
4566 const int a_level,
4567 const DataIndex a_din,
4568 const Box a_box,
4569 const Real a_dx) const noexcept
4570{
4571 CH_TIMERS("ItoKMCStepper::reconcileParticles(patch)");
4572 CH_TIMER("ItoKMCStepper::reconcileParticles(patch)::collect_ptr", t1);
4573 CH_TIMER("ItoKMCStepper::reconcileParticles(patch)::regular_cells", t2);
4574 CH_TIMER("ItoKMCStepper::reconcileParticles(patch)::irregular_cells", t3);
4575 if (m_verbosity > 5) {
4576 pout() << m_name + "::reconcileParticles(patch)" << endl;
4577 }
4578
4579 // clang-format off
4580 // TLDR: This is the main routine for generating new particles/photons after the chemistry advance have finished. We have
4581 // already computed the number of particles in each grid cell, and we now need to generate them. To do that we use
4582 // the reconciliation routines from the physics interface, which takes the per-cell responsibility for that. The main
4583 // work done in this routine is to expose the per-patch data to per-cell data that the physics interface can then use.
4584 // clang-format on
4585
4586 const int numItoSpecies = m_physics->getNumItoSpecies();
4587 const int numCdrSpecies = m_physics->getNumCdrSpecies();
4588 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4589
4590 CH_assert(a_newParticlesPerCell.nComp() == numItoSpecies);
4591 CH_assert(a_oldParticlesPerCell.nComp() == numItoSpecies);
4592 CH_assert(a_newPhotonsPerCell.nComp() == numPhotonSpecies);
4593 CH_assert(a_electricField.nComp() == SpaceDim);
4594
4595 // Geometric information that we need.
4596 const RealVect probLo = m_amr->getProbLo();
4597 const EBISBox& ebisbox = m_amr->getEBISLayout(m_particleRealm, m_plasmaPhase)[a_level][a_din];
4598
4599 CH_START(t1);
4600 // List of valid grid cells
4601 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_particleRealm)[a_level])[a_din];
4602
4603 // Regular electric field cells
4604 const FArrayBox& electricFieldReg = a_electricField.getFArrayBox();
4605
4606 // Per-cell SoA scratch: one ParticleSoA<P> per grid cell, indexed by Box::index(iv). The Ito bulk leaves
4607 // and the bulk/source photon leaves are cell-sorted and split into these; the mutated scratches are rebuilt
4608 // into the SoA leaves after the kernels run. The CDR photoionization products use ParticleSoA<NoPayload>.
4609 std::vector<std::vector<ParticleSoA<ItoParticle>>> itoCells(numItoSpecies);
4610 std::vector<std::vector<ParticleSoA<Photon>>> bulkPhotonCells(numPhotonSpecies);
4611 std::vector<std::vector<ParticleSoA<Photon>>> sourcePhotonCells(numPhotonSpecies);
4612 std::vector<std::vector<ParticleSoA<NoPayload>>> cdrCells(numCdrSpecies);
4613
4614 // Build the per-cell Ito scratch from the SoA bulk leaves.
4615 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
4616 const int idx = solverIt.index();
4617
4618 ParticleContainer<ItoParticle>& solverParticles = solverIt()->getParticles(ItoSolver::WhichContainer::Bulk);
4619
4620 binLeafToCells(itoCells[idx], solverParticles[a_level][a_din], a_box, a_dx, probLo);
4621 }
4622
4623 // Build the per-cell CDR photoionization-product scratch from the SoA leaves.
4624 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
4625 const int idx = solverIt.index();
4626
4627 binLeafToCells(cdrCells[idx], (*m_cdrProducts[idx])[a_level][a_din], a_box, a_dx, probLo);
4628 }
4629
4630 // Build the per-cell photon scratch from the SoA bulk/source photon leaves.
4631 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
4632 const int idx = solverIt.index();
4633
4634 ParticleContainer<Photon>& solverBulkPhotons = solverIt()->getBulkPhotons();
4635 ParticleContainer<Photon>& solverSourcePhotons = solverIt()->getSourcePhotons();
4636
4637 binLeafToCells(bulkPhotonCells[idx], solverBulkPhotons[a_level][a_din], a_box, a_dx, probLo);
4638 binLeafToCells(sourcePhotonCells[idx], solverSourcePhotons[a_level][a_din], a_box, a_dx, probLo);
4639 }
4640
4641 // The physics interface takes the physical number of particles/photons as arguments
4642 // to the reconciliation routines. These need to be set from the input arguments; this is the
4643 // storage we use in the grid cells.
4644 Vector<Physics::ItoKMC::FPR> numNewParticles(numItoSpecies);
4645 Vector<Physics::ItoKMC::FPR> numOldParticles(numItoSpecies);
4646 Vector<Physics::ItoKMC::FPR> numNewPhotons(numPhotonSpecies);
4647
4648 Vector<long long> numNewCdrParticles(numCdrSpecies);
4649
4650 // Under CdrProductInjection::Mesh the production never becomes particles, and a_cdrProduction is not filled at
4651 // all, so neither it nor the kernels below may be read.
4652 const bool injectCdrProducts = (m_cdrProductInjection == CdrProductInjection::Particle);
4653
4654 // The physics interface also takes the actual particles/photons as argument to its reconciliation routines. This
4655 // is the storage we use for these; note that it is repopulated in every grid cell (pointing at the per-cell
4656 // SoA scratch above). CDR products remain AoS lists.
4657 Vector<ParticleSoA<ItoParticle>*> itoParticles(numItoSpecies);
4658 Vector<ParticleSoA<NoPayload>*> cdrParticles(numCdrSpecies);
4659 Vector<ParticleSoA<Photon>*> bulkPhotons(numPhotonSpecies);
4660 Vector<ParticleSoA<Photon>*> sourcePhotons(numPhotonSpecies);
4661 CH_STOP(t1);
4662
4663 // Regular cells
4664 auto regularKernel = [&](const IntVect& iv) -> void {
4665 if (ebisbox.isRegular(iv) && validCells(iv)) {
4666 const RealVect electricField = RealVect(
4667 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
4668 const RealVect cellPos = probLo + a_dx * (RealVect(iv) + 0.5 * RealVect::Unit);
4669 const RealVect centroidPos = RealVect::Zero;
4670 const RealVect lo = -0.5 * RealVect::Unit;
4671 const RealVect hi = 0.5 * RealVect::Unit;
4672 const RealVect bndryCentroid = RealVect::Zero;
4673 const RealVect bndryNormal = RealVect::Zero;
4674 const Real kappa = 1.0;
4675
4676 // Populate the per-cell Ito data
4677 for (int i = 0; i < numItoSpecies; i++) {
4678 itoParticles[i] = &itoCells[i][a_box.index(iv)];
4679 numNewParticles[i] = llround(a_newParticlesPerCell.getSingleValuedFAB()(iv, i));
4680 numOldParticles[i] = llround(a_oldParticlesPerCell.getSingleValuedFAB()(iv, i));
4681 }
4682
4683 // Populate the per-cell CDR data
4684 for (int i = 0; i < numCdrSpecies; i++) {
4685 cdrParticles[i] = &cdrCells[i][a_box.index(iv)];
4686 numNewCdrParticles[i] = injectCdrProducts ? llround(a_cdrProduction.getSingleValuedFAB()(iv, i)) : 0LL;
4687 }
4688
4689 // Populate the per-cell photon data.
4690 for (int i = 0; i < numPhotonSpecies; i++) {
4691 bulkPhotons[i] = &bulkPhotonCells[i][a_box.index(iv)];
4692 sourcePhotons[i] = &sourcePhotonCells[i][a_box.index(iv)];
4693
4694 numNewPhotons[i] = llround(a_newPhotonsPerCell.getSingleValuedFAB()(iv, i));
4695
4696 // sourcePhotons will hold the NEW number of photons to be generated -- it should already
4697 // have been cleared in upstream code but I'm leaving this in for safety.
4698 sourcePhotons[i]->clear();
4699 }
4700
4701 // Turn the CDR production into particles, so that it reaches the mesh through the same deposition kernel as
4702 // the Ito products rather than landing in this cell alone. Deposited by depositCdrProducts(). A zero
4703 // production makes this a no-op, which is what CdrProductInjection::Mesh relies on.
4704 //
4705 // Runs BEFORE the Ito reconciliation so that both halves of a reaction see the same Ito particles. Under
4706 // ParticlePlacement::Downstream that is what keeps the downstream region identical for the two: the Ito
4707 // products below are placed downstream and so cannot move it, but the photoionization products are placed at
4708 // the absorption position and can.
4709 m_physics->reconcileCdrParticles(cdrParticles,
4710 itoParticles,
4711 numNewCdrParticles,
4712 electricField,
4713 cellPos,
4714 centroidPos,
4715 lo,
4716 hi,
4717 bndryCentroid,
4718 bndryNormal,
4719 a_dx,
4720 kappa);
4721
4722 // Reconcile the ItoSolver particles -- this either removes weight from the original particles (if we lost
4723 // physical particles) or adds new particles (if we gained physical particles)
4724 m_physics->reconcileParticles(itoParticles,
4725 numNewParticles,
4726 numOldParticles,
4727 electricField,
4728 cellPos,
4729 centroidPos,
4730 lo,
4731 hi,
4732 bndryCentroid,
4733 bndryNormal,
4734 a_dx,
4735 kappa);
4736
4737 // Reconcile the photon solver. This will generate new computational photons that are later added to the Monte
4738 // Carlo photon solvers.
4739 m_physics->reconcilePhotons(sourcePhotons,
4740 numNewPhotons,
4741 cellPos,
4742 centroidPos,
4743 lo,
4744 hi,
4745 bndryCentroid,
4746 bndryNormal,
4747 a_dx,
4748 kappa);
4749
4750 // Add the photoionization term. This will adds new particles from the photoionization reactions.
4751 m_physics->reconcilePhotoionization(itoParticles, cdrParticles, bulkPhotons);
4752
4753 // NOTE: Super-particle merging is NOT done here anymore. It is a separate step run after the
4754 // whole chemistry advance (see advanceReactionNetwork(Real)), through the public
4755 // ItoSolver::makeSuperparticles(), so that both per-cell and AMR-wide merge algorithms
4756 // are supported. It used to be done here only because the particles happened to be
4757 // cell-sorted for the reaction kernels.
4758 }
4759 };
4760
4761 // Irregular cells
4762 auto irregularKernel = [&](const VolIndex& vof) -> void {
4763 const IntVect iv = vof.gridIndex();
4764 if (ebisbox.isIrregular(iv) && validCells(iv, 0)) {
4765 const RealVect electricField = RealVect(
4766 D_DECL(a_electricField(vof, 0), a_electricField(vof, 1), a_electricField(vof, 2)));
4767 const RealVect cellPos = probLo + Location::position(Location::Cell::Center, vof, ebisbox, a_dx);
4768 const RealVect centroidPos = ebisbox.centroid(vof);
4769 const RealVect bndryCentroid = ebisbox.bndryCentroid(vof);
4770 const RealVect bndryNormal = ebisbox.normal(vof);
4771 const Real kappa = ebisbox.volFrac(vof);
4772
4773 // Compute the minimum bounding box that encloses this cut-cell.
4774 RealVect lo = -0.5 * RealVect::Unit;
4775 RealVect hi = 0.5 * RealVect::Unit;
4776 if (kappa < 1.0) {
4777 DataOps::computeMinValidBox(lo, hi, bndryNormal, bndryCentroid);
4778 }
4779
4780 // Populate the per-cell particle data.
4781 for (int i = 0; i < numItoSpecies; i++) {
4782 itoParticles[i] = &itoCells[i][a_box.index(iv)];
4783 numNewParticles[i] = llround(a_newParticlesPerCell(vof, i));
4784 numOldParticles[i] = llround(a_oldParticlesPerCell(vof, i));
4785 }
4786
4787 // Populate the per-cell CDR data
4788 for (int i = 0; i < numCdrSpecies; i++) {
4789 cdrParticles[i] = &cdrCells[i][a_box.index(iv)];
4790 numNewCdrParticles[i] = injectCdrProducts ? llround(a_cdrProduction(vof, i)) : 0LL;
4791 }
4792
4793 // Populate the per-cell photon data.
4794 for (int i = 0; i < numPhotonSpecies; i++) {
4795 bulkPhotons[i] = &bulkPhotonCells[i][a_box.index(iv)];
4796 sourcePhotons[i] = &sourcePhotonCells[i][a_box.index(iv)];
4797
4798 numNewPhotons[i] = llround(a_newPhotonsPerCell(vof, i));
4799
4800 // sourcePhotons will hold the NEW number of photons to be generated -- it should already
4801 // have been cleared in upstream code but I'm leaving this in for safety.
4802 sourcePhotons[i]->clear();
4803 }
4804
4805 // Turn the CDR production into particles -- see the regular kernel above.
4806 m_physics->reconcileCdrParticles(cdrParticles,
4807 itoParticles,
4808 numNewCdrParticles,
4809 electricField,
4810 cellPos,
4811 centroidPos,
4812 lo,
4813 hi,
4814 bndryCentroid,
4815 bndryNormal,
4816 a_dx,
4817 kappa);
4818
4819 // Reconcile the ItoSolver particles -- this either removes weight from the original particles (if we lost
4820 // physical particles) or adds new particles (if we gained physical particles)
4821 m_physics->reconcileParticles(itoParticles,
4822 numNewParticles,
4823 numOldParticles,
4824 electricField,
4825 cellPos,
4826 centroidPos,
4827 lo,
4828 hi,
4829 bndryCentroid,
4830 bndryNormal,
4831 a_dx,
4832 kappa);
4833
4834 // Reconcile the photon solver. This will generate new computational photons that are later added to the Monte
4835 // Carlo photon solvers.
4836 m_physics->reconcilePhotons(sourcePhotons,
4837 numNewPhotons,
4838 cellPos,
4839 centroidPos,
4840 lo,
4841 hi,
4842 bndryCentroid,
4843 bndryNormal,
4844 a_dx,
4845 kappa);
4846
4847 // Add the photoionization term. This will adds new particles from the photoionization reactions.
4848 m_physics->reconcilePhotoionization(itoParticles, cdrParticles, bulkPhotons);
4849
4850 // NOTE: Super-particle merging is done after the chemistry advance -- see the regular kernel
4851 // above and advanceReactionNetwork(Real).
4852 }
4853 };
4854
4855 // Run the kernels.
4856 VoFIterator& vofit = (*m_amr->getVofIterator(m_particleRealm, m_plasmaPhase)[a_level])[a_din];
4857
4858 CH_START(t2);
4859 BoxLoops::loop<D_DECL(1, 1, 1)>(a_box, regularKernel);
4860 CH_STOP(t2);
4861
4862 CH_START(t3);
4863 BoxLoops::loop(vofit, irregularKernel);
4864 CH_STOP(t3);
4865
4866 // SoA write-back: rebuild the Ito bulk leaves and the source-photon leaves from the mutated per-cell SoA
4867 // scratch (reconcile added/removed/merged Ito particles and generated source photons). The bulk-photon
4868 // scratch was read-only (photoionization), so it needs no write-back.
4869 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
4870 const int idx = solverIt.index();
4871
4872 ParticleContainer<ItoParticle>& solverParticles = solverIt()->getParticles(ItoSolver::WhichContainer::Bulk);
4873
4874 rebuildLeafFromCells(solverParticles[a_level][a_din], itoCells[idx], a_box, a_dx, probLo);
4875 }
4876
4877 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
4878 const int idx = solverIt.index();
4879
4880 ParticleContainer<Photon>& solverSourcePhotons = solverIt()->getSourcePhotons();
4881
4882 rebuildLeafFromCells(solverSourcePhotons[a_level][a_din], sourcePhotonCells[idx], a_box, a_dx, probLo);
4883 }
4884
4885 // Rebuild the CDR photoionization-product leaves from the mutated per-cell scratch.
4886 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
4887 const int idx = solverIt.index();
4888
4889 rebuildLeafFromCells((*m_cdrProducts[idx])[a_level][a_din], cdrCells[idx], a_box, a_dx, probLo);
4890 }
4891}
4892
4893template <typename I, typename C, typename R, typename F>
4894void
4896{
4897 CH_TIME("ItoKMCStepper::reconcilePhotoionization()");
4898 if (m_verbosity > 5) {
4899 pout() << m_name + "::reconcilePhotoionization()" << endl;
4900 }
4901
4902 const int numItoSpecies = m_physics->getNumItoSpecies();
4903 const int numCdrSpecies = m_physics->getNumCdrSpecies();
4904 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
4905
4906 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4907 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
4908 const DataIterator& dit = dbl.dataIterator();
4909
4910 const int nbox = dit.size();
4911
4912#pragma omp parallel for schedule(runtime)
4913 for (int mybox = 0; mybox < nbox; mybox++) {
4914 const DataIndex& din = dit[mybox];
4915
4916 // reconcilePhotoionization only APPENDS new Ito/CDR products (reading the absorbed bulk photons), so we
4917 // pass fresh empty Ito product containers and point the absorbed-photon args directly at the live bulk
4918 // photon leaves (read-only). The new Ito products are then bulk-appended to the SoA bulk leaves,
4919 // preserving the existing particles' full payload.
4920 Vector<ParticleSoA<ItoParticle>> itoProducts(numItoSpecies);
4921
4922 Vector<ParticleSoA<ItoParticle>*> itoParticles(numItoSpecies);
4923 Vector<ParticleSoA<NoPayload>*> cdrParticles(numCdrSpecies);
4924 Vector<ParticleSoA<Photon>*> photonParticles(numPhotonSpecies);
4925
4926 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
4927 itoParticles[solverIt.index()] = &itoProducts[solverIt.index()];
4928 }
4929
4930 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
4931 cdrParticles[solverIt.index()] = &((*m_cdrProducts[solverIt.index()])[lvl][din]);
4932 }
4933
4934 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
4935 photonParticles[solverIt.index()] = &(solverIt()->getBulkPhotons()[lvl][din]);
4936 }
4937
4938 m_physics->reconcilePhotoionization(itoParticles, cdrParticles, photonParticles);
4939
4940 // Append the new Ito photoionization products into the SoA bulk leaves (existing particles untouched).
4941 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
4942 const int idx = solverIt.index();
4943 ParticleSoA<ItoParticle>& leaf = solverIt()->getParticles(ItoSolver::WhichContainer::Bulk)[lvl][din];
4944
4945 leaf.append(itoProducts[idx]);
4946 }
4947 }
4948 }
4949}
4950
4951template <typename I, typename C, typename R, typename F>
4952void
4953ItoKMCStepper<I, C, R, F>::reconcileCdrDensities(const EBAMRCellData& a_cdrChange, const Real a_dt) noexcept
4954{
4955 CH_TIME("ItoKMCStepper::reconcileCdrDensities(EBAMRCellData, Real)");
4956 if (m_verbosity > 5) {
4957 pout() << m_name + "::reconcileCdrDensities(EBAMRCellData, Real)" << endl;
4958 }
4959
4960 const int numCdrSpecies = m_physics->getNumCdrSpecies();
4961
4962 CH_assert(a_cdrChange.getRealm() == m_fluidRealm);
4963 CH_assert(a_dt > 0.0);
4964
4965 if (numCdrSpecies > 0) {
4966
4967 // Increment, but don't divide by kappa.
4968 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4969 this->reconcileCdrDensities(*a_cdrChange[lvl], lvl, a_dt);
4970 }
4971
4972 // Redistribute if the user calls for it. Only under CdrProductInjection::Mesh: the redistribution corrects the
4973 // kappa damping that phi += change/V imposes on a cut-cell reaction, and under CdrProductInjection::Particle the
4974 // production no longer lands in the cell through that route -- it is deposited by the Ito solver, with the Ito
4975 // solver's own cut-cell normalization. See depositCdrProducts().
4976 if (m_redistributeCDR && m_cdrProductInjection == CdrProductInjection::Mesh) {
4977 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
4978 const int idx = it.index();
4979
4980 const EBAMRCellData cdrChange = m_amr->slice(a_cdrChange, Interval(idx, idx));
4981
4982 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4983 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[lvl];
4984 const DataIterator& dit = dbl.dataIterator();
4985 const EBISLayout& ebisl = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[lvl];
4986 const Real dx = m_amr->getDx()[lvl];
4987
4988 const int nbox = dit.size();
4989
4990#pragma omp parallel for schedule(runtime)
4991 for (int mybox = 0; mybox < nbox; mybox++) {
4992 const DataIndex& din = dit[mybox];
4993 const EBISBox& ebisbox = ebisl[din];
4994
4995 BaseIVFAB<Real>& deltaMass = (*m_fluidScratchEB[lvl])[din];
4996
4997 deltaMass.setVal(0.0);
4998
4999 const EBCellFAB& change = (*a_cdrChange[lvl])[din];
5000
5001 auto kernel = [&](const VolIndex& vof) -> void {
5002 const Real kappa = ebisbox.volFrac(vof);
5003
5004 deltaMass(vof, 0) = change(vof, idx) * (1.0 - kappa) / std::pow(dx, SpaceDim);
5005 };
5006
5007 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[lvl])[din];
5008
5009 BoxLoops::loop(vofit, kernel);
5010 }
5011 }
5012
5013 const RefCountedPtr<CdrSolver>& solver = it();
5014
5015 solver->redistribute(solver->getPhi(), m_fluidScratchEB);
5016 }
5017 }
5018
5019 // No ghost interpolation: nothing reads the density or source-term ghost cells between here and the
5020 // next transport step, which fills them itself. See coarsenCDRSolvers().
5021 this->coarsenCDRSolvers(false);
5022 }
5023}
5024
5025template <typename I, typename C, typename R, typename F>
5026void
5027ItoKMCStepper<I, C, R, F>::reconcileCdrDensities(const LevelData<EBCellFAB>& a_cdrChange,
5028 const int a_level,
5029 const Real a_dt) noexcept
5030{
5031 CH_TIME("ItoKMCStepper::reconcileCdrDensities(LD<EBCellFAB>, int, Real)");
5032 if (m_verbosity > 5) {
5033 pout() << m_name + "::reconcileCdrDensities(LD<EBCellFAB>, int, Real)" << endl;
5034 }
5035
5036 const int numCdrSpecies = m_physics->getNumCdrSpecies();
5037
5038 CH_assert(a_cdrChange.nComp() == numCdrSpecies);
5039
5040 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[a_level];
5041 const DataIterator& dit = dbl.dataIterator();
5042 const Real dx = m_amr->getDx()[a_level];
5043
5044 const int nbox = dit.size();
5045
5046#pragma omp parallel for schedule(runtime)
5047 for (int mybox = 0; mybox < nbox; mybox++) {
5048 const DataIndex& din = dit[mybox];
5049
5050 this->reconcileCdrDensities(a_cdrChange[din], a_level, din, dbl[din], dx, a_dt);
5051 }
5052}
5053
5054template <typename I, typename C, typename R, typename F>
5055void
5057 const int a_level,
5058 const DataIndex a_din,
5059 const Box a_box,
5060 const Real a_dx,
5061 const Real a_dt) noexcept
5062{
5063 CH_TIME("ItoKMCStepper::reconcileCdrDensities(EBCellFAB, int, DataIndex, Box, Realx2)");
5064 if (m_verbosity > 5) {
5065 pout() << m_name + "::reconcileCdrDensities(EBCellFAB, int, DataIndex, Box, Realx2)" << endl;
5066 }
5067
5068 const int numCdrSpecies = m_physics->getNumCdrSpecies();
5069
5070 CH_assert(a_cdrChange.nComp() == numCdrSpecies);
5071
5072 const Real volume = std::pow(a_dx, SpaceDim);
5073
5074 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
5075 RefCountedPtr<CdrSolver>& solver = solverIt();
5076 const int index = solverIt.index();
5077
5078 EBCellFAB& phi = (*(solver->getPhi()[a_level]))[a_din];
5079 EBCellFAB& src = (*(solver->getSource()[a_level]))[a_din];
5080
5081 // Source = change/volume
5082 src.setVal(0.0);
5083 src.plus(a_cdrChange, index, 0, 1);
5084 src /= volume;
5085
5086 // Phi += change/volume
5087 phi += src;
5088
5089 // Source = change/(volume*dt)
5090 src /= a_dt;
5091 }
5092}
5093
5094template <typename I, typename C, typename R, typename F>
5095void
5096ItoKMCStepper<I, C, R, F>::coarsenCDRSolvers(const bool a_interpGhosts) noexcept
5097{
5098 CH_TIME("ItoKMCStepper::coarsenCDRSolvers");
5099 if (m_verbosity > 5) {
5100 pout() << m_name + "::coarsenCDRSolvers" << endl;
5101 }
5102
5103 for (auto solverIt = this->m_cdr->iterator(); solverIt.ok(); ++solverIt) {
5104 auto& solver = solverIt();
5105
5106 EBAMRCellData& phi = solver->getPhi();
5107 EBAMRCellData& src = solver->getSource();
5108
5109 this->m_amr->conservativeAverage(phi, phi.getRealm(), this->m_plasmaPhase);
5110 this->m_amr->conservativeAverage(src, src.getRealm(), this->m_plasmaPhase);
5111
5112 // Density only. The source term's ghost cells have no reader anywhere -- see the note on the
5113 // declaration -- so interpolating them is a hierarchy-wide exchange per species per step for data
5114 // nothing looks at.
5115 if (a_interpGhosts) {
5116 this->m_amr->interpGhostPwl(phi, phi.getRealm(), this->m_plasmaPhase);
5117 }
5118
5119 DataOps::setCoveredValue(phi, this->m_amr->getCoveredCells(phi.getRealm(), this->m_plasmaPhase), 0.0);
5120 DataOps::setCoveredValue(src, this->m_amr->getCoveredCells(src.getRealm(), this->m_plasmaPhase), 0.0);
5121 }
5122}
5123
5124template <typename I, typename C, typename R, typename F>
5125bool
5127{
5128 CH_TIME("ItoKMCStepper::needSecondaryEmissionEB");
5129 if (m_verbosity > 5) {
5130 pout() << m_name + "::needSecondaryEmissionEB" << endl;
5131 }
5132
5133 // A mobile CDR species can deliver flux to the boundary with no particles or photons involved at all, so
5134 // its presence alone forces the emission to run. Static, hence no reduction.
5135 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
5136 if (solverIt()->isMobile()) {
5137 return true;
5138 }
5139 }
5140
5141 long long numPrimaries = 0;
5142
5143 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
5144 numPrimaries += static_cast<long long>(
5145 solverIt()->getParticles(ItoSolver::WhichContainer::EB).getNumberOfValidParticlesLocal());
5146 }
5147
5148 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
5149 numPrimaries += static_cast<long long>(solverIt()->getEbPhotons().getNumberOfValidParticlesLocal());
5150 }
5151
5152 return ParallelOps::sum(numPrimaries) > 0LL;
5153}
5154
5155template <typename I, typename C, typename R, typename F>
5156void
5158{
5159 CH_TIME("ItoKMCStepper::fillSecondaryEmissionEB(Real)");
5160 if (m_verbosity > 5) {
5161 pout() << m_name + "::fillSecondaryEmissionEB(Real)" << endl;
5162 }
5163
5164 // Particles that left the domain
5165 Vector<ParticleContainer<ItoParticle>*> primaryParticles;
5166 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5167 ParticleContainer<ItoParticle>& intersectedParticles = it()->getParticles(ItoSolver::WhichContainer::EB);
5168
5169 primaryParticles.push_back(&intersectedParticles);
5170 }
5171
5172 // CDR solvers extrapolate their fluxes. We then copy the extrapolated fluxes to transient data holders (which are
5173 // defined over the particle realm).
5174 EBAMRIVData tmp;
5175 m_amr->allocate(tmp, m_fluidRealm, m_plasmaPhase, 1);
5176
5177 for (auto solverIt = m_cdr->iterator(); solverIt.ok(); ++solverIt) {
5178 const int idx = solverIt.index();
5179 const RefCountedPtr<CdrSolver>& solver = solverIt();
5180
5181 EBAMRIVData& extrapFlux = m_cdrFluxesExtrap[idx];
5182
5183 if (solver->isMobile()) {
5184 solver->extrapolateAdvectiveFluxToEB(tmp);
5185
5186 m_amr->copyData(extrapFlux, tmp);
5187 }
5188 else {
5189 DataOps::setValue(extrapFlux, 0.0);
5190 }
5191 }
5192
5193 // Photons that left the domain
5194 Vector<ParticleContainer<Photon>*> primaryPhotons;
5195 for (auto it = m_rte->iterator(); it.ok(); ++it) {
5196 ParticleContainer<Photon>& intersectedPhotons = it()->getEbPhotons();
5197
5198 primaryPhotons.push_back(&intersectedPhotons);
5199 }
5200
5201 // Call the other version.
5202 this->fillSecondaryEmissionEB(m_secondaryParticles,
5203 m_cdrFluxes,
5204 m_secondaryPhotons,
5205 primaryParticles,
5206 m_cdrFluxesExtrap,
5207 primaryPhotons,
5208 m_electricFieldParticle,
5209 a_dt);
5210}
5211
5212template <typename I, typename C, typename R, typename F>
5213void
5215 Vector<RefCountedPtr<ParticleContainer<ItoParticle>>>& a_secondaryParticles,
5216 Vector<EBAMRIVData>& a_cdrFluxes,
5217 Vector<RefCountedPtr<ParticleContainer<Photon>>>& a_secondaryPhotons,
5218 Vector<ParticleContainer<ItoParticle>*>& a_primaryParticles,
5219 Vector<EBAMRIVData>& a_cdrFluxesExtrap,
5220 Vector<ParticleContainer<Photon>*>& a_primaryPhotons,
5221 const EBAMRCellData& a_electricField,
5222 const Real a_dt) noexcept
5223{
5224 CH_TIME("ItoKMCStepper::fillSecondaryEmissionEB(full)");
5225 if (m_verbosity > 5) {
5226 pout() << m_name + "::fillSecondaryEmissionEB(full)" << endl;
5227 }
5228
5229 const int numItoSpecies = m_physics->getNumItoSpecies();
5230 const int numCdrSpecies = m_physics->getNumCdrSpecies();
5231 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
5232
5233 CH_assert(a_secondaryParticles.size() == numItoSpecies);
5234 CH_assert(a_cdrFluxes.size() == numCdrSpecies);
5235 CH_assert(a_secondaryPhotons.size() == numPhotonSpecies);
5236 CH_assert(a_primaryParticles.size() == numItoSpecies);
5237 CH_assert(a_cdrFluxesExtrap.size() == numCdrSpecies);
5238 CH_assert(a_primaryPhotons.size() == numPhotonSpecies);
5239 CH_assert(a_electricField.getRealm() == m_particleRealm);
5240 CH_assert(a_dt >= 0.0);
5241
5242 // Incoming/outgoing particle containers must be sorted by cell.
5243 for (int i = 0; i < numItoSpecies; i++) {
5244 CH_assert(a_secondaryParticles[i]->getRealm() == m_particleRealm);
5245 CH_assert(a_primaryParticles[i]->getRealm() == m_particleRealm);
5246
5247 a_secondaryParticles[i]->clearParticles();
5248 a_secondaryParticles[i]->organizeParticlesByCell();
5249
5250 a_primaryParticles[i]->organizeParticlesByCell();
5251 }
5252
5253 for (int i = 0; i < numCdrSpecies; i++) {
5254 CH_assert(a_cdrFluxes[i].getRealm() == m_particleRealm);
5255 CH_assert(a_cdrFluxesExtrap[i].getRealm() == m_particleRealm);
5256
5257 DataOps::setValue(a_cdrFluxes[i], 0.0);
5258 }
5259
5260 // Incoming/outgoing photon containers by be sorted by cell
5261 for (int i = 0; i < numPhotonSpecies; i++) {
5262 CH_assert(a_secondaryPhotons[i]->getRealm() == m_particleRealm);
5263 CH_assert(a_primaryPhotons[i]->getRealm() == m_particleRealm);
5264
5265 a_secondaryPhotons[i]->clearParticles();
5266
5267 a_secondaryPhotons[i]->organizeParticlesByCell();
5268 a_primaryPhotons[i]->organizeParticlesByCell();
5269 }
5270
5271 const RealVect probLo = m_amr->getProbLo();
5272
5273 const Vector<Electrode>& electrodes = m_computationalGeometry->getElectrodes();
5274 const Vector<Dielectric>& dielectrics = m_computationalGeometry->getDielectrics();
5275
5276 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
5277 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
5278 const DataIterator& dit = dbl.dataIterator();
5279 const EBISLayout& ebisl = m_amr->getEBISLayout(m_particleRealm, m_plasmaPhase)[lvl];
5280 const Real dx = m_amr->getDx()[lvl];
5281
5282 const int nbox = dit.size();
5283
5284#pragma omp parallel for schedule(runtime)
5285 for (int mybox = 0; mybox < nbox; mybox++) {
5286 const DataIndex& din = dit[mybox];
5287
5288 // The kernel below is driven by the cut cells, so a patch without any has nothing to do. Its
5289 // secondary containers were cleared and its CDR fluxes zeroed before this loop, which is
5290 // exactly the state the patch would be left in anyway. Skipping matters because everything
5291 // below is per species and would otherwise be paid on every patch in the domain, whether or
5292 // not it is anywhere near the EB.
5293 VoFIterator& vofit = (*m_amr->getVofIterator(m_particleRealm, m_plasmaPhase)[lvl])[din];
5294
5295 if (vofit.size() == 0) {
5296 continue;
5297 }
5298
5299 const EBISBox& ebisbox = ebisl[din];
5300 const EBCellFAB& electricField = (*a_electricField[lvl])[din];
5301 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_particleRealm)[lvl])[din];
5302 const Box box = dbl[din];
5303
5304 bool isDielectric = false;
5305
5306 // Per-cell SoA scratch (indexed by Box::index(iv)). Primary cells are read from the SoA leaves;
5307 // secondary cells start empty and are rebuilt into the SoA leaves after the kernel.
5308 std::vector<std::vector<ParticleSoA<ItoParticle>>> primaryItoCells(numItoSpecies);
5309 std::vector<std::vector<ParticleSoA<ItoParticle>>> secondaryItoCells(numItoSpecies);
5310 std::vector<std::vector<ParticleSoA<Photon>>> primaryPhotonCells(numPhotonSpecies);
5311 std::vector<std::vector<ParticleSoA<Photon>>> secondaryPhotonCells(numPhotonSpecies);
5312
5313 Vector<BaseIVFAB<Real>*> cdrFluxesFAB;
5314 Vector<BaseIVFAB<Real>*> cdrFluxesExtrapFAB;
5315
5316 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5317 const int idx = it.index();
5318
5319 binLeafToCells(primaryItoCells[idx], (*a_primaryParticles[idx])[lvl][din], box, dx, probLo);
5320 secondaryItoCells[idx].resize(box.numPts());
5321 }
5322
5323 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
5324 cdrFluxesFAB.push_back(&((*(a_cdrFluxes[it.index()])[lvl])[din]));
5325 cdrFluxesExtrapFAB.push_back(&((*(a_cdrFluxesExtrap[it.index()])[lvl])[din]));
5326 }
5327
5328 for (auto it = m_rte->iterator(); it.ok(); ++it) {
5329 const int idx = it.index();
5330
5331 binLeafToCells(primaryPhotonCells[idx], (*a_primaryPhotons[idx])[lvl][din], box, dx, probLo);
5332 secondaryPhotonCells[idx].resize(box.numPts());
5333 }
5334
5335 // Kernel definition.
5336 auto irregularKernel = [&](const VolIndex& vof) -> void {
5337 const IntVect iv = vof.gridIndex();
5338
5339 if (validCells(iv)) {
5340 const RealVect E = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
5341 const RealVect bndryNormal = ebisbox.normal(vof);
5342 const RealVect bndryCentroid = ebisbox.bndryCentroid(vof);
5343 const RealVect cellCentroid = ebisbox.centroid(vof);
5344 const RealVect cellCenter = probLo + Location::position(Location::Cell::Center, vof, ebisbox, dx);
5345 const RealVect physPos = cellCenter + bndryCentroid * dx;
5346 const Real bndryArea = ebisbox.bndryArea(vof);
5347
5348 const long cellIdx = box.index(iv);
5349
5350 // Secondary containers start empty; the kernel appends to them. Primary cells are moved in from the
5351 // per-cell scratch (read-only input; their leaves are not written back).
5352 Vector<ParticleSoA<ItoParticle>> secondaryParticles(numItoSpecies);
5353 Vector<ParticleSoA<ItoParticle>> primaryParticles(numItoSpecies);
5354
5355 Vector<Real> cdrFluxes(numCdrSpecies, 0.0);
5356 Vector<Real> cdrFluxesExtrap(numCdrSpecies, 0.0);
5357
5358 Vector<ParticleSoA<Photon>> secondaryPhotons(numPhotonSpecies);
5359 Vector<ParticleSoA<Photon>> primaryPhotons(numPhotonSpecies);
5360
5361 for (int i = 0; i < numItoSpecies; i++) {
5362 primaryParticles[i] = std::move(primaryItoCells[i][cellIdx]);
5363 }
5364
5365 // Populate CDR fluxes
5366 for (int i = 0; i < numCdrSpecies; i++) {
5367 cdrFluxes[i] = 0.0;
5368 cdrFluxesExtrap[i] = (*cdrFluxesExtrapFAB[i])(vof, 0);
5369 }
5370
5371 for (int i = 0; i < numPhotonSpecies; i++) {
5372 primaryPhotons[i] = std::move(primaryPhotonCells[i][cellIdx]);
5373 }
5374
5375 // Figure out which material we are dealing with.
5376 int matIndex = -1;
5377 Real minDist = std::numeric_limits<Real>::max();
5378
5379 for (int i = 0; i < electrodes.size(); i++) {
5380 const Real curDist = electrodes[i].getImplicitFunction()->value(physPos);
5381
5382 if (std::abs(curDist) < std::abs(minDist)) {
5383 minDist = curDist;
5384 matIndex = i;
5385 }
5386 }
5387
5388 for (int i = 0; i < dielectrics.size(); i++) {
5389 const Real curDist = dielectrics[i].getImplicitFunction()->value(physPos);
5390
5391 if (std::abs(curDist) < std::abs(minDist)) {
5392 minDist = curDist;
5393 matIndex = i;
5394 isDielectric = true;
5395 }
5396 }
5397
5398 // Call the physics framework.
5399 m_physics->secondaryEmissionEB(secondaryParticles,
5400 cdrFluxes,
5401 secondaryPhotons,
5402 primaryParticles,
5403 cdrFluxesExtrap,
5404 primaryPhotons,
5405 E,
5406 cellCenter,
5407 cellCentroid,
5408 bndryCentroid,
5409 bndryNormal,
5410 bndryArea,
5411 dx,
5412 a_dt,
5413 isDielectric,
5414 matIndex);
5415
5416 // Fill output data holders (move the per-cell secondary results into the per-cell scratch).
5417 for (int i = 0; i < numItoSpecies; i++) {
5418 secondaryItoCells[i][cellIdx] = std::move(secondaryParticles[i]);
5419 }
5420
5421 for (int i = 0; i < numCdrSpecies; i++) {
5422 (*cdrFluxesFAB[i])(vof, 0) = cdrFluxes[i];
5423 }
5424
5425 for (int i = 0; i < numPhotonSpecies; i++) {
5426 secondaryPhotonCells[i][cellIdx] = std::move(secondaryPhotons[i]);
5427 }
5428 }
5429 };
5430
5431 // Run the kernel.
5432 BoxLoops::loop(vofit, irregularKernel);
5433
5434 // SoA write-back: rebuild the secondary Ito/photon leaves for this patch from the filled per-cell scratch.
5435 // Primaries are read-only (the physics interface takes them by const reference), so they are not written.
5436 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5437 const int idx = it.index();
5438 rebuildLeafFromCells((*a_secondaryParticles[idx])[lvl][din], secondaryItoCells[idx], box, dx, probLo);
5439 }
5440 for (auto it = m_rte->iterator(); it.ok(); ++it) {
5441 const int idx = it.index();
5442 rebuildLeafFromCells((*a_secondaryPhotons[idx])[lvl][din], secondaryPhotonCells[idx], box, dx, probLo);
5443 }
5444 }
5445 }
5446
5447 // Sort by patch
5448 for (int i = 0; i < numItoSpecies; i++) {
5449 a_secondaryParticles[i]->organizeParticlesByPatch();
5450 a_primaryParticles[i]->organizeParticlesByPatch();
5451 }
5452
5453 for (int i = 0; i < numPhotonSpecies; i++) {
5454 a_secondaryPhotons[i]->organizeParticlesByPatch();
5455 a_primaryPhotons[i]->organizeParticlesByPatch();
5456 }
5457}
5458
5459template <typename I, typename C, typename R, typename F>
5460void
5462{
5463 CH_TIME("ItoKMCStepper::resolveSecondaryEmissionEB(short)");
5464 if (m_verbosity > 5) {
5465 pout() << m_name + "::resolveSecondaryEmissionEB(short)" << endl;
5466 }
5467
5468 Vector<ParticleContainer<ItoParticle>*> secondaryParticles;
5469 Vector<ParticleContainer<ItoParticle>*> primaryParticles;
5470
5471 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5472 const int idx = it.index();
5473
5474 primaryParticles.push_back(&(it()->getParticles(ItoSolver::WhichContainer::EB)));
5475 secondaryParticles.push_back(&(*m_secondaryParticles[idx]));
5476 }
5477
5478 // Copy the CDR fluxes on the particle realm over to the fluid realm.
5479 Vector<EBAMRIVData*> cdrFluxes;
5480 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
5481 const RefCountedPtr<CdrSolver>& solver = it();
5482 const int idx = it.index();
5483
5484 EBAMRIVData& ebFlux = solver->getEbFlux();
5485
5486 m_amr->copyData(ebFlux, m_cdrFluxes[idx]);
5487
5488 m_amr->arithmeticAverage(ebFlux, m_fluidRealm, m_plasmaPhase);
5489
5490 cdrFluxes.push_back(&ebFlux);
5491 }
5492
5493 // Handle to surface charge density.
5494 EBAMRIVData& surfaceChargeDensity = m_sigmaSolver->getPhi();
5495
5496 this->resolveSecondaryEmissionEB(secondaryParticles, primaryParticles, cdrFluxes, surfaceChargeDensity, a_dt);
5497
5498 m_sigmaSolver->resetElectrodes(0.0);
5499 m_amr->arithmeticAverage(surfaceChargeDensity, m_fluidRealm, m_plasmaPhase);
5500}
5501
5502template <typename I, typename C, typename R, typename F>
5503void
5505 Vector<ParticleContainer<ItoParticle>*>& a_primaryParticles,
5506 Vector<EBAMRIVData*>& a_cdrFluxes,
5507 EBAMRIVData& a_surfaceChargeDensity,
5508 const Real a_dt) noexcept
5509{
5510 CH_TIME("ItoKMCStepper::resolveSecondaryEmissionEB(full)");
5511 if (m_verbosity > 5) {
5512 pout() << m_name + "::resolveSecondaryEmissionEB(full)" << endl;
5513 }
5514
5515 const int numItoSpecies = m_physics->getNumItoSpecies();
5516 const int numCdrSpecies = m_physics->getNumCdrSpecies();
5517
5518 CH_assert(a_secondaryParticles.size() == numItoSpecies);
5519 CH_assert(a_primaryParticles.size() == numItoSpecies);
5520 CH_assert(a_cdrFluxes.size() == numCdrSpecies);
5521 CH_assert(a_surfaceChargeDensity.getRealm() == m_fluidRealm);
5522
5523 for (int i = 0; i < numItoSpecies; i++) {
5524 CH_assert(a_secondaryParticles[i]->getRealm() == m_particleRealm);
5525 CH_assert(a_primaryParticles[i]->getRealm() == m_particleRealm);
5526 }
5527
5528 for (int i = 0; i < numCdrSpecies; i++) {
5529 CH_assert(a_secondaryParticles[i]->getRealm() == m_particleRealm);
5530 CH_assert(a_primaryParticles[i]->getRealm() == m_particleRealm);
5531 }
5532
5533 // Deposit the incoming/outgoing particles on the surface and update the surface charge density.
5534 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5535 const RefCountedPtr<ItoSolver>& solver = it();
5536 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
5537
5538 const int idx = it.index();
5539 const int Z = species->getChargeNumber();
5540
5541 if (Z != 0) {
5542
5543 // Add charge from primary particles
5544 m_amr->depositParticles(m_particleScratchEB, m_particleRealm, m_plasmaPhase, *a_primaryParticles[idx]);
5545
5546 m_amr->copyData(m_fluidScratchEB, m_particleScratchEB);
5547 DataOps::incr(a_surfaceChargeDensity,
5548 m_fluidScratchEB,
5549 1.0 * Z * Units::Qe,
5550 m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase));
5551
5552 // Subtract charge from secondary particles
5553 m_amr->depositParticles(m_particleScratchEB, m_particleRealm, m_plasmaPhase, *a_secondaryParticles[idx]);
5554
5555 m_amr->copyData(m_fluidScratchEB, m_particleScratchEB);
5556 DataOps::incr(a_surfaceChargeDensity,
5557 m_fluidScratchEB,
5558 -1.0 * Z * Units::Qe,
5559 m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase));
5560 }
5561
5562 // Add the secondary particles into the solvers and remove the primary particles.
5563 ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
5564 particles.transferParticles(a_secondaryParticles[idx]->getParticles());
5565
5566 a_primaryParticles[idx]->clearParticles();
5567
5568 if (a_secondaryParticles[idx]->getNumberOfValidParticlesGlobal() > 0) {
5569 MayDay::Abort("logic bust");
5570 }
5571 }
5572
5573 // Add CDR fluxes to the CDR solvers
5574 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
5575 const RefCountedPtr<CdrSolver>& solver = it();
5576 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
5577
5578 const int idx = it.index();
5579 const int Z = species->getChargeNumber();
5580
5581 if (Z != 0) {
5582 DataOps::incr(a_surfaceChargeDensity,
5583 *a_cdrFluxes[idx],
5584 Z * a_dt * Units::Qe,
5585 m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase));
5586 }
5587
5588 // Add mass to CDR solvers -- this is an inefficient way of doing it but I don't know if it'll be a performance
5589 // bottleneck as well.
5590 EBAMRCellData divG;
5591 EBAMRFluxData G;
5592
5593 m_amr->allocate(divG, m_fluidRealm, m_plasmaPhase, 1);
5594 m_amr->allocate(G, m_fluidRealm, m_plasmaPhase, 1);
5595
5596 DataOps::setValue(G, 0.0);
5597
5598 solver->computeDivG(divG, G, *a_cdrFluxes[idx], false);
5599
5600 EBAMRCellData& phi = solver->getPhi();
5601 DataOps::incr(phi, divG, -a_dt);
5602
5603 m_amr->conservativeAverage(phi, m_fluidRealm, m_plasmaPhase);
5604 m_amr->interpGhostPwl(phi, m_fluidRealm, m_plasmaPhase);
5605
5606 // Really don't want negative densities.
5607 DataOps::floor(phi, 0.0, m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase));
5608 }
5609
5610 // Conservatively coarsen the surface charge density.
5611 m_amr->conservativeAverage(a_surfaceChargeDensity, m_fluidRealm, m_plasmaPhase);
5612}
5613
5614template <typename I, typename C, typename R, typename F>
5615void
5617{
5618 CH_TIME("ItoKMCStepper::computePhysicsDt()");
5619 if (m_verbosity > 5) {
5620 pout() << m_name + "::computePhysicsDt()" << endl;
5621 }
5622
5623 Real maxDt = std::numeric_limits<Real>::max();
5624 Real minDt = std::numeric_limits<Real>::max();
5625
5626 DataOps::getMaxMin(maxDt, minDt, m_kmcDt, 0, m_amr->getMultiCutVofIterator(m_fluidRealm, m_plasmaPhase));
5627
5628 m_physicsDt = minDt;
5629}
5630
5631template <typename I, typename C, typename R, typename F>
5632void
5634{
5635 CH_TIME("ItoKMCStepper::computeDummyPhysicsDt()");
5636 if (m_verbosity > 5) {
5637 pout() << m_name + "::computeDummyPhysicsDt()" << endl;
5638 }
5639
5640 const int numItoSpecies = m_physics->getNumItoSpecies();
5641 const int numCdrSpecies = m_physics->getNumCdrSpecies();
5642 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
5643
5644 // Sort by cells
5645 (this->m_ito)->organizeParticlesByCell(ItoSolver::WhichContainer::Bulk);
5646 this->sortPhotonsByCell(McPhoto::WhichContainer::Bulk);
5647 this->sortPhotonsByCell(McPhoto::WhichContainer::Source);
5648
5649 // Compute the number of reactive particles for both Ito and CDR species. Also do a backup of the initial number
5650 // of particles per cell. This is required when reconciling the results lateron.
5651 if (numItoSpecies > 0) {
5652 this->computeReactiveItoParticlesPerCell(m_particleItoPPC);
5653
5654 const Interval srcInterv(0, numItoSpecies - 1);
5655 const Interval dstInterv(0, numItoSpecies - 1);
5656
5657 m_amr->copyData(m_fluidPPC, m_particleItoPPC, dstInterv, srcInterv);
5658
5659 DataOps::copy(m_particleOldItoPPC, m_particleItoPPC);
5660 }
5661 if (numCdrSpecies > 0) {
5662 this->computeReactiveCdrParticlesPerCell(m_fluidCdrPPC);
5663
5664 const Interval srcInterv(0, numCdrSpecies - 1);
5665 const Interval dstInterv(numItoSpecies, numItoSpecies + numCdrSpecies - 1);
5666
5667 m_amr->copyData(m_fluidPPC, m_fluidCdrPPC, dstInterv, srcInterv);
5668
5669 DataOps::copy(m_fluidOldCdrPPC, m_fluidCdrPPC);
5670 }
5671
5672 DataOps::setValue(m_fluidYPC, 0.0);
5673 DataOps::setValue(m_particleYPC, 0.0);
5674
5675 // Advance the reaction network using a zero time step, which should not trigger any change in the state vector.
5676 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
5677 this->advanceReactionNetwork(*m_fluidPPC[lvl], *m_fluidYPC[lvl], *m_electricFieldFluid[lvl], lvl, 0.0);
5678 }
5679
5680 // Reorganize into patch
5681 (this->m_ito)->organizeParticlesByPatch(ItoSolver::WhichContainer::Bulk);
5682 this->sortPhotonsByPatch(McPhoto::WhichContainer::Bulk);
5683 this->sortPhotonsByPatch(McPhoto::WhichContainer::Source);
5684
5685 this->computePhysicsDt();
5686}
5687
5688template <typename I, typename C, typename R, typename F>
5689Real
5691{
5692 CH_TIME("ItoKMCStepper::computeTotalCharge()");
5693 if (m_verbosity > 5) {
5694 pout() << m_name + "::computeTotalCharge()" << endl;
5695 }
5696
5697 const bool kappaScale = true;
5698
5699 Real totalCharge = 0.0;
5700
5701 totalCharge += this->computeQplus();
5702 totalCharge += this->computeQminu();
5703 totalCharge += this->computeQsurf();
5704
5705 return totalCharge;
5706}
5707
5708template <typename I, typename C, typename R, typename F>
5709Real
5711{
5712 CH_TIME("ItoKMCStepper::computeQplus()");
5713 if (m_verbosity > 5) {
5714 pout() << m_name + "::computeQplus()" << endl;
5715 }
5716
5717 const bool kappaScale = true;
5718
5719 Real totalCharge = 0.0;
5720
5721 // Charge from Ito solvers.
5722 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5723 const RefCountedPtr<ItoSolver>& solver = it();
5724 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
5725
5726 const int Z = species->getChargeNumber();
5727
5728 if (Z > 0) {
5729 const ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
5730
5731 totalCharge += Z * ParticleOps::sum(particles);
5732 }
5733 }
5734
5735 // Charge from CDR solvers
5736 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
5737 const RefCountedPtr<CdrSolver>& solver = it();
5738 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
5739
5740 const int Z = species->getChargeNumber();
5741
5742 if (Z > 0) {
5743 const EBAMRCellData& phi = solver->getPhi();
5744
5745 totalCharge += Z * solver->computeMass(phi, kappaScale);
5746 }
5747 }
5748
5749 return totalCharge * Units::Qe;
5750}
5751
5752template <typename I, typename C, typename R, typename F>
5753Real
5755{
5756 CH_TIME("ItoKMCStepper::computeQminu()");
5757 if (m_verbosity > 5) {
5758 pout() << m_name + "::computeQminu()" << endl;
5759 }
5760
5761 const bool kappaScale = true;
5762
5763 Real totalCharge = 0.0;
5764
5765 // Charge from Ito solvers.
5766 for (auto it = m_ito->iterator(); it.ok(); ++it) {
5767 const RefCountedPtr<ItoSolver>& solver = it();
5768 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
5769
5770 const int Z = species->getChargeNumber();
5771
5772 if (Z < 0) {
5773 const ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
5774
5775 totalCharge += Z * ParticleOps::sum(particles);
5776 }
5777 }
5778
5779 // Charge from CDR solvers
5780 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
5781 const RefCountedPtr<CdrSolver>& solver = it();
5782 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
5783
5784 const int Z = species->getChargeNumber();
5785
5786 if (Z < 0) {
5787 const EBAMRCellData& phi = solver->getPhi();
5788
5789 totalCharge += Z * solver->computeMass(phi, kappaScale);
5790 }
5791 }
5792
5793 return totalCharge * Units::Qe;
5794}
5795
5796template <typename I, typename C, typename R, typename F>
5797Real
5799{
5800 CH_TIME("ItoKMCStepper::computeQsurf()");
5801 if (m_verbosity > 5) {
5802 pout() << m_name + "::computeQsurf()" << endl;
5803 }
5804
5805 return m_sigmaSolver->computeMass();
5806}
5807
5808template <typename I, typename C, typename R, typename F>
5809void
5811{
5812 CH_TIME("ItoKMCStepper::advancePhotons(Real)");
5813 if (m_verbosity > 5) {
5814 pout() << m_name + "::advancePhotons(Real)" << endl;
5815 }
5816
5817 // TLDR: This will add the source photons to the "bulk" photons and then advance them. If the
5818 // solver is a true transient solver then the photons are moved and some of them are eventually
5819 // absorbed on the mesh. If the solver is an "instanteneous" solver then all source photons
5820 // are absorbed on the mesh.
5821
5822 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
5823 RefCountedPtr<McPhoto>& solver = solverIt();
5824
5825 // To reiterate: photons are the photons that live in the solver and are moved around. bulkPhotons
5826 // are the solvers that were absorbed on the mesh, bbPhotons are the photons that collided with the EB
5827 // and domainPhotons are photons that moved out of the domain.
5828 ParticleContainer<Photon>& photons = solver->getPhotons();
5829 ParticleContainer<Photon>& bulkPhotons = solver->getBulkPhotons();
5830 ParticleContainer<Photon>& ebPhotons = solver->getEbPhotons();
5831 ParticleContainer<Photon>& domainPhotons = solver->getDomainPhotons();
5832 ParticleContainer<Photon>& sourcePhotons = solver->getSourcePhotons();
5833
5834 solver->clear(bulkPhotons);
5835 solver->clear(ebPhotons);
5836 solver->clear(domainPhotons);
5837
5838 if (solver->isInstantaneous()) {
5839 solver->clear(photons);
5840
5841 // Add source Photons
5842 photons.transferParticles(sourcePhotons.getParticles());
5843 solver->clear(sourcePhotons);
5844
5845 // Instantaneous advance.
5846 solver->advancePhotonsInstantaneous(bulkPhotons, ebPhotons, domainPhotons, photons);
5847 }
5848 else {
5849 // Add source Photons
5850 photons.transferParticles(sourcePhotons.getParticles());
5851 solver->clear(sourcePhotons);
5852
5853 // Stationary advance
5854 solver->advancePhotonsTransient(bulkPhotons, ebPhotons, domainPhotons, photons, a_dt);
5855 }
5856 }
5857}
5858
5859template <typename I, typename C, typename R, typename F>
5860void
5862{
5863 CH_TIME("ItoKMCStepper::sortPhotonsByCell(McPhoto::WhichContainer)");
5864 if (m_verbosity > 5) {
5865 pout() << m_name + "::sortPhotonsByCell(McPhoto::WhichContainer)" << endl;
5866 }
5867
5868 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
5869 solverIt()->sortPhotonsByCell(a_which);
5870 }
5871}
5872
5873template <typename I, typename C, typename R, typename F>
5874void
5876{
5877 CH_TIME("ItoKMCStepper::sortPhotonsByPatch(McPhoto::WhichContainer)");
5878 if (m_verbosity > 5) {
5879 pout() << m_name + "::sortPhotonsByPatch(McPhoto::WhichContainer)" << endl;
5880 }
5881
5882 for (auto solverIt = m_rte->iterator(); solverIt.ok(); ++solverIt) {
5883 solverIt()->sortPhotonsByPatch(a_which);
5884 }
5885}
5886
5887template <typename I, typename C, typename R, typename F>
5888Vector<RefCountedPtr<ItoSolver>>
5890{
5891 CH_TIME("ItoKMCStepper::getLoadBalanceSolvers()");
5892 if (m_verbosity > 5) {
5893 pout() << m_name + "::getLoadBalanceSolvers()" << endl;
5894 }
5895
5896 Vector<RefCountedPtr<ItoSolver>> lbSolvers;
5897
5898 // If there's an index < 0 we load balance everything.
5899 bool loadBalanceAll = false;
5900 for (int i = 0; i < m_loadBalanceIndices.size(); i++) {
5901 if (m_loadBalanceIndices[i] < 0) {
5902 loadBalanceAll = true;
5903 }
5904 }
5905
5906 if (loadBalanceAll) {
5907 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
5908 lbSolvers.push_back(solverIt());
5909 }
5910 }
5911 else {
5912 for (int i = 0; i < m_loadBalanceIndices.size(); i++) {
5913 RefCountedPtr<ItoSolver>& solver = m_ito->getSolvers()[i];
5914
5915 lbSolvers.push_back(solver);
5916 }
5917 }
5918
5919 return lbSolvers;
5920}
5921
5922template <typename I, typename C, typename R, typename F>
5923bool
5925{
5926 CH_TIME("TimeStepper::loadBalanceThisRealm");
5927 if (m_verbosity > 5) {
5928 pout() << "TimeStepper::loadBalanceThisRealm" << endl;
5929 }
5930
5931 bool ret = false;
5932
5933 if (a_realm == m_particleRealm && m_loadBalanceParticles) {
5934 ret = true;
5935 }
5936 else if (a_realm == m_fluidRealm && m_loadBalanceFluid) {
5937 ret = true;
5938 }
5939
5940 return ret;
5941}
5942
5943template <typename I, typename C, typename R, typename F>
5944void
5946 Vector<Vector<Box>>& a_boxes,
5947 const std::string& a_realm,
5948 const Vector<DisjointBoxLayout>& a_grids,
5949 const int a_lmin,
5950 const int a_finestLevel)
5951{
5952 CH_TIME("ItoKMCStepper::loadBalanceBoxes");
5953 if (m_verbosity > 5) {
5954 pout() << m_name + "::loadBalanceBoxes" << endl;
5955 }
5956
5957 if (m_loadBalanceParticles && a_realm == m_particleRealm) {
5958 this->loadBalanceParticleRealm(a_procs, a_boxes, a_realm, a_grids, a_lmin, a_finestLevel);
5959 }
5960 else if (m_loadBalanceFluid && a_realm == m_fluidRealm) {
5961 this->loadBalanceFluidRealm(a_procs, a_boxes, a_realm, a_grids, a_lmin, a_finestLevel);
5962 }
5963}
5964
5965template <typename I, typename C, typename R, typename F>
5966void
5968 Vector<Vector<Box>>& a_boxes,
5969 const std::string a_realm,
5970 const Vector<DisjointBoxLayout>& a_grids,
5971 const int a_lmin,
5972 const int a_finestLevel) noexcept
5973{
5974 CH_TIME("ItoKMCStepper::loadBalanceParticleRealm(...)");
5975 if (m_verbosity > 5) {
5976 pout() << m_name + "::loadBalanceParticleRealm(...)" << endl;
5977 }
5978
5979 // TLDR: This is a bit involved due to the fact that the simulation lives in a state between the old grids
5980 // and the new grids. We want to compute the number of computational in patch on the new grid, and
5981 // use that for load balancing. We have already computed the number of computational particles per
5982 // grid cell on the old grids, but the new grids are not ready (yet). We only have the proxy-grids
5983 // coming in through the argument (a_grids), and our job is to take this grid and reassign the patches
5984 // so that each MPI rank gets roughly the same number of computational particles. To do this we perform
5985 // the following steps:
5986 //
5987 // 1. Allocate storage on the proxy grids (a_grids) so we have something to regrid into.
5988 // 2. Define regrid operators for going between the proxy grids and the old grids.
5989 // 3. Regrid the PPC on the old grids onto the proxy grids.
5990 // 4. Go through the patches on the proxy grids and figure out the total number of particles
5991 // in each patch (there's a weird global-to-local remapping taking place through intCode()).
5992 // 5. Call our nifty load-balancing routines.
5993 //
5994
5995 if (!m_loadBalanceParticles) {
5996 MayDay::Error("ItoKMCStepper::loadBalanceParticleRealm -- logic bust, should not have been called!");
5997 }
5998
5999 // Get the solvers that we will use for load balancing.
6000 Vector<RefCountedPtr<ItoSolver>> lbSolvers = this->getLoadBalanceSolvers();
6001
6002 // Decompose the DisjointBoxLayout
6003 a_procs.resize(1 + a_finestLevel);
6004 a_boxes.resize(1 + a_finestLevel);
6005
6006 for (int lvl = a_lmin; lvl <= a_finestLevel; lvl++) {
6007 a_procs[lvl] = a_grids[lvl].procIDs();
6008 a_boxes[lvl] = a_grids[lvl].boxArray();
6009 }
6010
6011 // 1. Allocate something that we can regrid the PPC for each species into, and something that holds the total
6012 // PPC on the new grids.
6013 EBAMRCellData totalPPC;
6014 EBAMRCellData speciesPPC;
6015
6016 m_amr->allocate(totalPPC, m_particleRealm, m_plasmaPhase, 1);
6017 m_amr->allocate(speciesPPC, m_particleRealm, m_plasmaPhase, 1);
6018
6019 DataOps::setValue(totalPPC, 0.0);
6020 DataOps::setValue(speciesPPC, 0.0);
6021
6022 // 2. EBCoarseToFineInterp is not a part of the registry for ItoSolver so we just define it here ourselves. Note that
6023 // it is stored on the same level that we interpolate to.
6024 Vector<RefCountedPtr<EBCoarseToFineInterp>> interpOp(1 + a_finestLevel);
6025 for (int lvl = 1; lvl <= a_finestLevel; lvl++) {
6026 const EBLevelGrid& eblgFine = *m_amr->getEBLevelGrid(m_particleRealm, m_plasmaPhase)[lvl];
6027 const EBLevelGrid& eblgCoFi = *m_amr->getEBLevelGridCoFi(m_particleRealm, m_plasmaPhase)[lvl - 1];
6028 const EBLevelGrid& eblgCoar = *m_amr->getEBLevelGrid(m_particleRealm, m_plasmaPhase)[lvl - 1];
6029 const int refRat = m_amr->getRefinementRatios()[lvl - 1];
6030
6031 interpOp[lvl] = RefCountedPtr<EBCoarseToFineInterp>(new EBCoarseToFineInterp(eblgFine, eblgCoFi, eblgCoar, refRat));
6032 }
6033
6034 // 3. Go through each solver and figure out the number of particles on the new grids. Add
6035 // these to totalPPC.
6036 for (int i = 0; i < lbSolvers.size(); i++) {
6037 const EBAMRCellData& oldData = m_loadBalancePPC[i];
6038 const int oldFinestLevel = oldData.size() - 1;
6039
6040 // These levels have not changed but ownship MIGHT have changed.
6041 for (int lvl = 0; lvl <= std::max(0, a_lmin - 1); lvl++) {
6042 oldData[lvl]->copyTo(*speciesPPC[lvl]);
6043 }
6044
6045 // These levels have changed.
6046 for (int lvl = std::max(1, a_lmin); lvl <= a_finestLevel; lvl++) {
6047 RefCountedPtr<EBCoarseToFineInterp>& interpolator = interpOp[lvl];
6048
6049 interpolator->interpolate(*speciesPPC[lvl],
6050 *speciesPPC[lvl - 1],
6051 Interval(0, 0),
6052 EBCoarseToFineInterp::Type::ConservativePWC);
6053
6054 // There could be parts of the new grid that overlapped with the old grid (on level lvl) -- we don't want
6055 // to pollute the solution with interpolation there since we already have valid data.
6056 if (lvl <= std::min(oldFinestLevel, a_finestLevel)) {
6057 oldData[lvl]->copyTo(*speciesPPC[lvl]);
6058 }
6059 }
6060
6061 // Add to totalPPC.
6062 DataOps::incr(totalPPC, speciesPPC, 1.0);
6063 }
6064
6065 // 4. totalPPC contains the total number of computational particles per cell on the new grids,
6066 // we need to map this to something we can load balance.
6067 Vector<Vector<long int>> loads(1 + a_finestLevel, 0L);
6068 for (int lvl = 0; lvl <= a_finestLevel; lvl++) {
6069 const DisjointBoxLayout& dbl = a_grids[lvl];
6070 const DataIterator& dit = dbl.dataIterator();
6071
6072 Vector<long int>& levelLoads = loads[lvl];
6073
6074 levelLoads.resize(dbl.size());
6075
6076 const int nbox = dit.size();
6077
6078#pragma omp parallel for schedule(runtime)
6079 for (int mybox = 0; mybox < nbox; mybox++) {
6080 const DataIndex& din = dit[mybox];
6081
6082 const Box cellBox = dbl[din];
6083 const EBCellFAB& PPC = (*totalPPC[lvl])[din];
6084 const EBISBox& ebisbox = PPC.getEBISBox();
6085 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_particleRealm)[lvl])[din];
6086 const FArrayBox& regPPC = PPC.getFArrayBox();
6087
6088 auto regularKernel = [&](const IntVect& iv) -> void {
6089 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
6090 levelLoads[din.intCode()] += (long int)regPPC(iv, 0);
6091 }
6092 };
6093
6094 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
6095 }
6096
6097 ParallelOps::sum(levelLoads);
6098
6099 // Add the "constant" load from the other PPC stuff
6100 for (LayoutIterator lit = dbl.layoutIterator(); lit.ok(); ++lit) {
6101 const Box cellBox = dbl[lit()];
6102
6103 levelLoads[lit().intCode()] += (long int)m_loadPerCell * cellBox.numPts();
6104 }
6105 }
6106
6107 // 5. Finally do the actual load balancing.
6108 LoadBalancing::sort(a_boxes, loads, m_boxSort);
6109
6110 Loads rankLoads;
6111 rankLoads.resetLoads();
6112
6113 for (int lvl = 0; lvl <= a_finestLevel; lvl++) {
6114 LoadBalancing::makeBalance(a_procs[lvl], rankLoads, loads[lvl], a_boxes[lvl]);
6115 }
6116}
6117
6118template <typename I, typename C, typename R, typename F>
6119void
6121 Vector<Vector<Box>>& a_boxes,
6122 const std::string a_realm,
6123 const Vector<DisjointBoxLayout>& a_grids,
6124 const int a_lmin,
6125 const int a_finestLevel) noexcept
6126{
6127 CH_TIME("ItoKMCStepper::loadBalanceFluidRealm(...)");
6128 if (m_verbosity > 5) {
6129 pout() << m_name + "::loadBalanceFluidRealm(...)" << endl;
6130 }
6131
6132 CH_assert(m_loadBalanceFluid);
6133 CH_assert(a_realm == m_fluidRealm);
6134
6135 // clang-format off
6136 // TLDR: This code tries to compute a load for each grid patch by applying a relaxation operator to each box. This means that the load
6137 // should be a decent estimate that takes into account boundary conditions, coarse-fine interface arithmetic, and enlargened stencils
6138 // near the embedded boundary.
6139 // clang-format on
6140
6141 a_procs.resize(1 + a_finestLevel);
6142 a_boxes.resize(1 + a_finestLevel);
6143
6144 // We need to make AmrMesh restore some operators that we need in order to create a multigrid object. Fortunately,
6145 // FieldSolver has routines for doing that but it will not know if AmrMesh has updated it's operators or not. So, we
6146 // need to regrid them.
6147 m_amr->regridOperators(m_fluidRealm, a_lmin);
6148
6149 // Field solver needs to allocate solver and set up the multigrid solver.
6150 m_fieldSolver->allocate();
6151 m_fieldSolver->setupSolver();
6152
6153 // Loads on each rank
6154 Loads rankLoads;
6155 rankLoads.resetLoads();
6156
6157 // Field solver implementation gets the responsibility of computing loads on each level.
6158 for (int lvl = 0; lvl <= a_finestLevel; lvl++) {
6159 Vector<long long> boxLoads = m_fieldSolver->computeLoads(a_grids[lvl], lvl);
6160
6161 // Do the desired sorting and load balancing
6162 a_boxes[lvl] = a_grids[lvl].boxArray();
6163
6164 LoadBalancing::sort(a_boxes[lvl], boxLoads, m_boxSort);
6165 LoadBalancing::makeBalance(a_procs[lvl], rankLoads, boxLoads, a_boxes[lvl]);
6166 }
6167}
6168
6169template <typename I, typename C, typename R, typename F>
6170Vector<long int>
6171ItoKMCStepper<I, C, R, F>::getCheckpointLoads(const std::string& a_realm, const int a_level) const
6172{
6173 CH_TIME("ItoKMCStepper::getCheckpointLoads(...)");
6174 if (m_verbosity > 5) {
6175 pout() << m_name + "::getCheckpointLoads(...)" << endl;
6176 }
6177
6178 const DisjointBoxLayout& dbl = m_amr->getGrids(a_realm)[a_level];
6179 const int nbox = dbl.size();
6180
6181 Vector<long int> loads(nbox, 0L);
6182
6183 if (m_loadBalanceParticles && a_realm == m_particleRealm) {
6184
6185 // If we're load balancing with particles, get the number of particles per patch
6186 // from the relevant particle solvers. Since these are Ito solvers, the loads
6187 // are equal to the number of computational particles in the grid patches.
6188 Vector<RefCountedPtr<ItoSolver>> loadBalanceProxySolvers = this->getLoadBalanceSolvers();
6189
6190 for (int isolver = 0; isolver < loadBalanceProxySolvers.size(); isolver++) {
6191
6192 // This solver computes loads -- there's a parallel gather operation
6193 // under the hood here.
6194 Vector<long int> solverLoads(nbox, 0L);
6195 loadBalanceProxySolvers[isolver]->computeLoads(solverLoads, dbl, a_level);
6196
6197 // Add to total loads.
6198 for (int ibox = 0; ibox < nbox; ibox++) {
6199 loads[ibox] += solverLoads[ibox];
6200 }
6201 }
6202
6203 // Add the "constant" loads -- these are computational loads due to the "mesh" part. We use
6204 // a heuristic where we have m_loadPerCell "cost".
6205 for (LayoutIterator lit = dbl.layoutIterator(); lit.ok(); ++lit) {
6206 const Box box = dbl[lit()];
6207
6208 loads[lit().intCode()] += lround(m_loadPerCell * box.numPts());
6209 }
6210 }
6211 else {
6212 loads = TimeStepper::getCheckpointLoads(a_realm, a_level);
6213 }
6214
6215 return loads;
6216}
6217
6218template <typename I, typename C, typename R, typename F>
6219void
6221{
6222 CH_TIME("ItoKMCStepper::computeEdotJSource(a_dt)");
6223 if (m_verbosity > 5) {
6224 pout() << m_name + "::computeEdotJSource(a_dt)" << endl;
6225 }
6226
6227 CH_assert(a_dt > 0.0);
6228
6229 DataOps::setValue(m_EdotJ, 0.0);
6230
6231 CH_assert(m_EdotJ.getRealm() == m_fluidRealm);
6232
6233 // clang-format off
6234 // TLDR: EdotJ is an energy term for the various species, i.e. it is the rate of energy increase as the particle moves from
6235 // position A to position B, excluding friction from collision with other molecules. We compute this energy increase as
6236 //
6237 // q * V(B) - V(A)
6238 //
6239 // which means that the energy rate is q*(V(B) - V(A))/a_dt.
6240 //
6241 // We simply assign this factor to the particles and then deposit them on the mesh. However, this is more complex than
6242 // it sounds because the particles m_EdotJ live on different realms. The way we do this is that we copy the potential
6243 // over into the particle realm and we interpolate V(B) and V(A) onto some storage in the particle container. We then
6244 // assign an effective weight w * [V(B) - V(A)] to the particles which we deposit onto the mesh using the appropriate
6245 // deposition scheme that the user has assgned.
6246 //
6247 // clang-format on
6248
6249 // Allocate a transient SoA particle holder. The container owns the weight and position; the payload holds
6250 // V(A), V(B), and the per-component "other" position (alt) so we can interpolate the potential at both A and B.
6251 ParticleContainer<ItoKMCFieldParticle> computationParticles;
6252 m_amr->allocate(computationParticles, m_particleRealm);
6253
6254 // Electrostatic potential on appropriate phase. This is defined on the fluid realm
6255 // but we need it on the particle realm.
6256 const EBAMRCellData potentialPhase = m_amr->alias(m_plasmaPhase, m_fieldSolver->getPotential());
6257 m_amr->copyData(m_particleScratch1, potentialPhase);
6258
6259 m_amr->conservativeAverage(m_particleScratch1, m_particleRealm, m_plasmaPhase);
6260 m_amr->interpGhost(m_particleScratch1, m_particleRealm, m_plasmaPhase);
6261
6262 for (auto solverIt = m_ito->iterator(); solverIt.ok(); ++solverIt) {
6263 RefCountedPtr<ItoSolver>& solver = solverIt();
6264 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
6265
6266 const int idx = solverIt.index();
6267 const int Z = species->getChargeNumber();
6268 const bool mobile = solver->isMobile();
6269 const bool diffusive = solver->isDiffusive();
6270
6271 if (Z != 0 && (mobile || diffusive)) {
6272
6273 const ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
6274
6275 // Copy the ItoParticles to the transient particles we use for computing these things. The container
6276 // position holds B (the current position); the payload "alt" columns hold A (the old position).
6277 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
6278 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
6279 const DataIterator& dit = dbl.dataIterator();
6280
6281 const int nbox = dit.size();
6282
6283#pragma omp parallel for schedule(runtime)
6284 for (int mybox = 0; mybox < nbox; mybox++) {
6285 const DataIndex& din = dit[mybox];
6286
6287 ParticleSoA<ItoKMCFieldParticle>& comp = computationParticles[lvl][din];
6288 const ParticleSoA<ItoParticle>& leaf = particles[lvl][din];
6289
6290 for (std::size_t i = 0; i < leaf.size(); i++) {
6291 const RealVect posA = RealVect(D_DECL(leaf.template get<&ItoParticle::old_x>(i),
6292 leaf.template get<&ItoParticle::old_y>(i),
6293 leaf.template get<&ItoParticle::old_z>(i)));
6294
6295 // No cast to ParticleReal: x0 is a position and stays double, so casting would round the
6296 // old position through float whenever PARTICLE_PRECISION=FLOAT. This one compiles either
6297 // way, which is why it survived where the pointer bindings below did not.
6298 ItoKMCFieldParticle payload;
6299 D_DECL(payload.x0_x = posA[0], payload.x0_y = posA[1], payload.x0_z = posA[2]);
6300
6301 comp.append(leaf.position(i), leaf.weight(i), payload);
6302 }
6303 }
6304 }
6305
6306 // Interpolate the potential to the current particle position which gives us V(B) for the particles.
6307 m_amr->interpolateParticles<&ItoKMCFieldParticle::phiB>(computationParticles,
6308 m_particleRealm,
6309 m_plasmaPhase,
6310 m_particleScratch1,
6311 solver->getDeposition(),
6312 false);
6313
6314 // Move the particles back to their old positions (A) and interpolate the potential there. The container
6315 // position and the payload "alt" position are swapped so position now holds A and alt holds B.
6316 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
6317 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
6318 const DataIterator& dit = dbl.dataIterator();
6319
6320 const int nbox = dit.size();
6321
6322#pragma omp parallel for schedule(runtime)
6323 for (int mybox = 0; mybox < nbox; mybox++) {
6324 const DataIndex& din = dit[mybox];
6325
6326 ParticleSoA<ItoKMCFieldParticle>& comp = computationParticles[lvl][din];
6327
6328 double* const pos[SpaceDim] = {
6329 D_DECL(comp.positionColumn(0), comp.positionColumn(1), comp.positionColumn(2))};
6330 // double rather than ParticleReal: x0 is a position, and positions are double at every
6331 // PARTICLE_PRECISION setting -- see ItoKMCFieldParticle. Typing these as ParticleReal
6332 // compiles only while the payload precision happens to be double.
6333 double* const alt[SpaceDim] = {D_DECL(comp.template column<&ItoKMCFieldParticle::x0_x>(),
6334 comp.template column<&ItoKMCFieldParticle::x0_y>(),
6335 comp.template column<&ItoKMCFieldParticle::x0_z>())};
6336
6337 ParticleLoops::loop(comp, [&](const std::size_t i) {
6338 for (int dir = 0; dir < SpaceDim; dir++) {
6339 const double posB = pos[dir][i];
6340 pos[dir][i] = alt[dir][i];
6341 alt[dir][i] = posB;
6342 }
6343 });
6344 }
6345 }
6346
6347 computationParticles.remap();
6348
6349 // Interpolate the potential to the previous particle position which gives us V(A) for the particles.
6350 m_amr->interpolateParticles<&ItoKMCFieldParticle::phiA>(computationParticles,
6351 m_particleRealm,
6352 m_plasmaPhase,
6353 m_particleScratch1,
6354 solver->getDeposition(),
6355 false);
6356
6357 // Move the particles back to B again and multiply the weight by (V(B) - V(A)).
6358 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
6359 const DisjointBoxLayout& dbl = m_amr->getGrids(m_particleRealm)[lvl];
6360 const DataIterator& dit = dbl.dataIterator();
6361
6362 const int nbox = dit.size();
6363
6364#pragma omp parallel for schedule(runtime)
6365 for (int mybox = 0; mybox < nbox; mybox++) {
6366 const DataIndex& din = dit[mybox];
6367
6368 ParticleSoA<ItoKMCFieldParticle>& comp = computationParticles[lvl][din];
6369
6370 double* const pos[SpaceDim] = {
6371 D_DECL(comp.positionColumn(0), comp.positionColumn(1), comp.positionColumn(2))};
6372 // See the swap above: x0 is a position, hence double rather than ParticleReal.
6373 const double* const alt[SpaceDim] = {D_DECL(comp.template column<&ItoKMCFieldParticle::x0_x>(),
6374 comp.template column<&ItoKMCFieldParticle::x0_y>(),
6375 comp.template column<&ItoKMCFieldParticle::x0_z>())};
6376 double* const w = comp.weightColumn();
6377 const ParticleReal* const phiA = comp.template column<&ItoKMCFieldParticle::phiA>();
6378 const ParticleReal* const phiB = comp.template column<&ItoKMCFieldParticle::phiB>();
6379
6380 ParticleLoops::loop(comp, [&](const std::size_t i) {
6381 for (int dir = 0; dir < SpaceDim; dir++) {
6382 pos[dir][i] = alt[dir][i];
6383 }
6384
6385 // Effective weight = weight * (V(B) - V(A)).
6386 w[i] *= (phiB[i] - phiA[i]);
6387 });
6388 }
6389 }
6390
6391 computationParticles.remap();
6392
6393 // Deposit the effective (energy-weighted) weight.
6394 m_amr->depositWeight(m_particleScratch1,
6395 m_particleRealm,
6396 m_plasmaPhase,
6397 computationParticles,
6398 solver->getDeposition(),
6399 solver->getCoarseFineDeposition(),
6401
6402 // Copy data back onto the fluid realm.
6403 m_amr->copyData(m_fluidScratch1, m_particleScratch1);
6404 DataOps::scale(m_fluidScratch1, Z * Units::Qe / a_dt);
6405 DataOps::plus(m_EdotJ, m_fluidScratch1, 0, idx, 1);
6406 }
6407
6408 computationParticles.clearParticles();
6409 }
6410}
6411
6412template <typename I, typename C, typename R, typename F>
6413void
6414ItoKMCStepper<I, C, R, F>::computePhysicsPlotVariables(EBAMRCellData& a_physicsPlotVars) noexcept
6415{
6416 CH_TIME("ItoKMCStepper::computePhysicsPlotVariables");
6417 if (m_verbosity > 5) {
6418 pout() << m_name + "::computePhysicsPlotVariables" << endl;
6419 }
6420
6421 // Number of output variables from CdrPlasmaPhysics
6422 const int numVars = m_physics->getNumberOfPlotVariables();
6423 const int numItoSpecies = m_physics->getNumItoSpecies();
6424 const int numCdrSpecies = m_physics->getNumCdrSpecies();
6425 const int numPlasmaSpecies = m_physics->getNumPlasmaSpecies();
6426 const int numPhotonSpecies = m_physics->getNumPhotonSpecies();
6427
6428 CH_assert(!(a_physicsPlotVars[0].isNull()));
6429 CH_assert(a_physicsPlotVars[0]->nComp() == numVars);
6430 CH_assert(a_physicsPlotVars.getRealm() == m_fluidRealm);
6431
6432 // Update gradients
6433 this->computeDensityGradients();
6434
6435 const RealVect probLo = m_amr->getProbLo();
6436
6437 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
6438 const DisjointBoxLayout& dbl = m_amr->getGrids(m_fluidRealm)[lvl];
6439 const DataIterator& dit = dbl.dataIterator();
6440 const EBISLayout& ebisl = m_amr->getEBISLayout(m_fluidRealm, m_plasmaPhase)[lvl];
6441 const Real dx = m_amr->getDx()[lvl];
6442
6443 const int nbox = dit.size();
6444
6445#pragma omp parallel for schedule(runtime)
6446 for (int mybox = 0; mybox < nbox; mybox++) {
6447 const DataIndex& din = dit[mybox];
6448
6449 const Box& cellBox = dbl[din];
6450 const EBISBox& ebisBox = ebisl[din];
6451
6452 // Handle to electric field
6453 const EBCellFAB& electricField = (*m_electricFieldFluid[lvl])[din];
6454 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
6455
6456 // Handle to densities and density gradients for CDR and Ito species.
6457 Vector<const EBCellFAB*> densitiesIto(numItoSpecies);
6458 Vector<const EBCellFAB*> densityGradientsIto(numItoSpecies);
6459 Vector<const FArrayBox*> densitiesItoReg(numItoSpecies);
6460 Vector<const FArrayBox*> densityGradientsItoReg(numItoSpecies);
6461
6462 Vector<const EBCellFAB*> densitiesCDR(numCdrSpecies);
6463 Vector<const EBCellFAB*> densityGradientsCDR(numCdrSpecies);
6464 Vector<const FArrayBox*> densitiesCDRReg(numCdrSpecies);
6465 Vector<const FArrayBox*> densityGradientsCDRReg(numCdrSpecies);
6466
6467 for (auto it = m_ito->iterator(); it.ok(); ++it) {
6468 const RefCountedPtr<ItoSolver>& solver = it();
6469
6470 const int i = it.index();
6471
6472 densitiesIto[i] = &(*(m_fluidPhiIto[i])[lvl])[din];
6473 densitiesItoReg[i] = &(densitiesIto[i]->getFArrayBox());
6474 densityGradientsIto[i] = &(*m_fluidGradPhiIto[i][lvl])[din];
6475 densityGradientsItoReg[i] = &(densityGradientsIto[i]->getFArrayBox());
6476 }
6477
6478 for (auto it = m_cdr->iterator(); it.ok(); ++it) {
6479 const RefCountedPtr<CdrSolver>& solver = it();
6480 const EBAMRCellData& phi = solver->getPhi();
6481
6482 const int i = it.index();
6483
6484 densitiesCDR[i] = &(*phi[lvl])[din];
6485 densitiesCDRReg[i] = &(densitiesCDR[i]->getFArrayBox());
6486 densityGradientsCDR[i] = &(*m_fluidGradPhiCDR[i][lvl])[din];
6487 densityGradientsCDRReg[i] = &(densityGradientsCDR[i]->getFArrayBox());
6488 }
6489
6490 // Handle to valid grid cells.
6491 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_fluidRealm)[lvl])[din];
6492
6493 // Handle to output variables
6494 EBCellFAB& physicsPlotVars = (*a_physicsPlotVars[lvl])[din];
6495 FArrayBox& physicsPlotVarsReg = physicsPlotVars.getFArrayBox();
6496
6497 // Things that will be populated in the kernels.
6498 Vector<Real> densities(numPlasmaSpecies);
6499 Vector<RealVect> densityGradients(numPlasmaSpecies);
6500
6501 // Regular cells
6502 auto regularKernel = [&](const IntVect& iv) -> void {
6503 if (ebisBox.isRegular(iv) && validCells(iv, 0)) {
6504 const RealVect pos = probLo + dx * (RealVect(iv) + 0.5 * RealVect::Unit);
6505 const RealVect E = RealVect(
6506 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
6507
6508 // Populate gradients.
6509 for (int i = 0; i < numItoSpecies; i++) {
6510 densities[i] = (*densitiesItoReg[i])(iv, 0);
6511 densityGradients[i] = RealVect(D_DECL((*densityGradientsItoReg[i])(iv, 0),
6512 (*densityGradientsItoReg[i])(iv, 1),
6513 (*densityGradientsItoReg[i])(iv, 2)));
6514 }
6515
6516 for (int i = 0; i < numCdrSpecies; i++) {
6517 densities[numItoSpecies + i] = (*densitiesCDRReg[i])(iv, 0);
6518 densityGradients[numItoSpecies + i] = RealVect(D_DECL((*densityGradientsCDRReg[i])(iv, 0),
6519 (*densityGradientsCDRReg[i])(iv, 1),
6520 (*densityGradientsCDRReg[i])(iv, 2)));
6521 }
6522
6523 // Do the physics advance.
6524 const Vector<Real> plotVars = m_physics->getPlotVariables(E, pos, densities, densityGradients, dx, 1.0);
6525
6526 CH_assert(plotVars.size() == numVars);
6527
6528 for (int i = 0; i < numVars; i++) {
6529 physicsPlotVarsReg(iv, i) = plotVars[i];
6530 }
6531 }
6532 };
6533
6534 // Irregular cells
6535 auto irregularKernel = [&](const VolIndex& vof) -> void {
6536 if (validCells(vof.gridIndex(), 0)) {
6537 const RealVect pos = probLo + dx * (RealVect(vof.gridIndex()) + 0.5 * RealVect::Unit);
6538 const RealVect E = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
6539
6540 // Populate gradients.
6541 for (int i = 0; i < numItoSpecies; i++) {
6542 densities[i] = (*densitiesIto[i])(vof, 0);
6543 densityGradients[i] = RealVect(D_DECL((*densityGradientsIto[i])(vof, 0),
6544 (*densityGradientsIto[i])(vof, 1),
6545 (*densityGradientsIto[i])(vof, 2)));
6546 }
6547
6548 for (int i = 0; i < numCdrSpecies; i++) {
6549 densities[numItoSpecies + i] = (*densitiesCDR[i])(vof, 0);
6550 densityGradients[numItoSpecies + i] = RealVect(D_DECL((*densityGradientsCDR[i])(vof, 0),
6551 (*densityGradientsCDR[i])(vof, 1),
6552 (*densityGradientsCDR[i])(vof, 2)));
6553 }
6554
6555 // Do the physics advance.
6556 const Vector<Real> plotVars = m_physics->getPlotVariables(E, pos, densities, densityGradients, dx, 1.0);
6557
6558 CH_assert(plotVars.size() == numVars);
6559
6560 for (int i = 0; i < numVars; i++) {
6561 physicsPlotVars(vof, i) = plotVars[i];
6562 }
6563 }
6564 };
6565
6566 // Run the kernels.
6567 VoFIterator& vofit = (*m_amr->getVofIterator(m_fluidRealm, m_plasmaPhase)[lvl])[din];
6568
6569 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
6570 BoxLoops::loop(vofit, irregularKernel);
6571 }
6572 }
6573
6574 m_amr->average(a_physicsPlotVars, m_fluidRealm, m_plasmaPhase, Average::Arithmetic, Interval(0, numVars - 1));
6575 m_amr->interpGhost(a_physicsPlotVars, m_fluidRealm, m_plasmaPhase);
6576}
6577
6578#include <CD_NamespaceFooter.H>
6579
6580#endif
Average
Various averaging methods.
Definition CD_Average.H:25
Agglomeration of useful data operations.
Declaration of an aggregated class for regrid operations.
EBIntersection
Enum for putting some logic into how we think about intersection between particles and EBs.
Definition CD_EBIntersection.H:22
EBRepresentation
Enum for putting some logic into how we think about EBs. This is just a simply supporting class for v...
Definition CD_EBRepresentation.H:23
@ Native
Deposit as-is, with no cut-cell treatment at all.
@ NGP
Put the particle's entire cloud in its own cell when that cell is a cut cell.
SoA payload for the transient E-dot-J energy computation in ItoKMCStepper.
SpeciesType
Tag for distinguishing species solved with an Ito diffusion or CDR fluid formalism.
Definition CD_ItoKMCPhysics.H:76
Declaration of the Physics::ItoKMC::ItoKMCStepper abstract TimeStepper.
SpeciesSubset
Enum for selecting a subset of plasma species by mobility/diffusion/charge properties.
Definition CD_ItoKMCStepper.H:43
Declaration of cell positions.
Agglomeration of basic MPI reductions.
Declaration of a namespace for SIMD-decorated loops over SoA particles.
Declaration of a static class containing some common useful particle routines that would otherwise be...
CD_PARTICLE_REAL ParticleReal
Floating-point type a user may use for payload columns.
Definition CD_ParticleSoA.H:156
Implementation of CD_Timer.H.
Declaration of various useful units.
Factory class for CdrLayout. T is (usually) CdrSolver and S is the implementation class (e....
Definition CD_CdrLayout.H:316
RefCountedPtr< CdrLayout< T > > newLayout(const Vector< RefCountedPtr< CdrSpecies > > &a_species) const
Factory method, create a new CdrLayout.
Definition CD_CdrLayoutImplem.H:547
Iterator class for CdrLayout. This allows iteration through solvers (or subsets of solvers).
Definition CD_CdrIterator.H:29
static void scale(MFAMRCellData &a_lhs, const Real &a_scale) noexcept
Scale data by factor.
Definition CD_DataOps.cpp:2564
static void floor(EBAMRCellData &a_lhs, const Real a_value, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Floor values in data holder. This sets all values below a_value to a_value.
Definition CD_DataOps.cpp:1526
static void getMaxMin(Real &max, Real &min, EBAMRCellData &a_data, const int a_comp, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Get maximum and minimum value of specified component.
Definition CD_DataOps.cpp:1772
static void volumeScale(EBAMRCellData &a_data, const Vector< Real > &a_dx)
Scale data by dx^SpaceDim.
Definition CD_DataOps.cpp:2297
static void getMaxMinNorm(Real &a_max, Real &a_min, EBAMRCellData &data, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Get maximum and minimum value of normed data.
Definition CD_DataOps.cpp:1940
static void roof(EBAMRCellData &a_lhs, const Real a_value, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Roof values in data holder. This sets all values above a_value to a_value.
Definition CD_DataOps.cpp:1617
static void computeMinValidBox(RealVect &a_lo, RealVect &a_hi, const RealVect &a_normal, const RealVect &a_centroid)
Compute the tightest possible valid box around a cut-cell volume.
Definition CD_DataOps.cpp:3748
static void vectorLength(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs, const EBAMRCellData &a_notCovered, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Compute the vector length of a data holder. Sets a_lhs = |a_rhs| where a_rhs contains SpaceDim compon...
Definition CD_DataOps.cpp:3561
static void incr(MFAMRCellData &a_lhs, const MFAMRCellData &a_rhs, const Real a_scale) noexcept
Function which increments data in the form a_lhs = a_lhs + a_rhs*a_scale for all components.
Definition CD_DataOps.cpp:881
static void setValue(LevelData< MFInterfaceFAB< T > > &a_lhs, const T &a_value)
Set value in an MFInterfaceFAB data holder.
Definition CD_DataOpsImplem.H:24
static void divideFallback(EBAMRCellData &a_numerator, const EBAMRCellData &a_denominator, const Real a_fallback, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Divide data. If the denominator is zero, set the value to a fallback option.
Definition CD_DataOps.cpp:1448
static void setCoveredValue(EBAMRCellData &a_lhs, const EBAMRCellData &a_coveredMask, const int a_comp, const Real a_value)
Set value in covered cells. Does specified component.
Definition CD_DataOps.cpp:2716
static void plus(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs, const int a_srcComp, const int a_dstComp, const int a_numComp)
General addition operator for adding together data. The user can choose which components to add.
Definition CD_DataOps.cpp:946
static void copy(MFAMRCellData &a_dst, const MFAMRCellData &a_src)
Copy data from one data holder to another.
Definition CD_DataOps.cpp:1262
static void averageCellToFace(EBAMRFluxData &a_faceData, const EBAMRCellData &a_cellData, const Vector< ProblemDomain > &a_domains, Vector< RefCountedPtr< LayoutData< std::array< FaceIterator, SpaceDim > > > > &a_faceIter)
Average all components of the cell-centered data to faces (arithmetic, no tangential ghost faces).
Definition CD_DataOps.cpp:149
static void multiplyScalar(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs)
Multiply data holder by another data holder.
Definition CD_DataOps.cpp:2402
Class for interpolating data to fine grids. Can use constant interpolation or include limiters.
Definition CD_EBCoarseToFineInterp.H:33
Factory class for making ItoLayout.
Definition CD_ItoLayout.H:412
RefCountedPtr< ItoLayout< T > > newLayout(const Vector< RefCountedPtr< ItoSpecies > > &a_species) const
Factory method which creates a new layout from a set of species. This can do automated casting betwee...
Definition CD_ItoLayoutImplem.H:438
"Iterator" class for going through solvers in an ItoLayout.
Definition CD_ItoIterator.H:26
WhichContainer
Enum class for distinguishing various types of particle containers.
Definition CD_ItoSolver.H:52
static void makeBalance(Vector< int > &a_ranks, const Vector< T > &a_loads, const Vector< Box > &a_boxes)
Load balancing, assigning ranks to boxes.
Definition CD_LoadBalancingImplem.H:36
static void sort(Vector< Vector< Box > > &a_boxes, Vector< Vector< T > > &a_loads, const BoxSorting a_whichSorting)
Sorts boxes and loads over a hierarchy according to some sorting criterion.
Definition CD_LoadBalancingImplem.H:227
Class for holding computational loads.
Definition CD_Loads.H:31
virtual void resetLoads() noexcept
Reset loads. Sets all loads to 0.
Definition CD_Loads.cpp:55
WhichContainer
Enum class for identifying various containers. Only used for interface reasons.
Definition CD_McPhoto.H:43
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
void clearParticles()
Drop all valid particles on every level (keeps each leaf's arena capacity).
Definition CD_ParticleContainer.H:442
void transferParticles(AMRParticlesSoA< P, Traits > &a_source)
Move all particles from another holder (on the same valid grids) into the valid holder.
Definition CD_ParticleContainer.H:804
AMRParticlesSoA< P, Traits > & getParticles()
The valid particles on all levels.
Definition CD_ParticleContainer.H:317
static Real sum(const ParticleContainer< P, Traits > &a_particles) noexcept
Global sum of the container-owned weight column (SoA overload).
Definition CD_ParticleOpsImplem.H:302
static void getComputationalParticlesPerCell(EBAMRCellData &a_ppc, const ParticleContainer< P, Traits > &a_src) noexcept
Get the number of computational particles per cell (SoA overload).
Definition CD_ParticleOpsImplem.H:85
static void getPhysicalParticlesPerCell(EBAMRCellData &a_ppc, const ParticleContainer< P, Traits > &a_src) noexcept
Get the number of physical particles per cell (SoA overload).
Definition CD_ParticleOpsImplem.H:53
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
void sortByCell(const Box &a_box, const RealVect &a_dx, const RealVect &a_probLo)
Counting-sort the columns into Fortran cell order and build CSR cell offsets.
Definition CD_ParticleSoAImplem.H:303
void append(const RealVect &a_position, const double a_weight)
Append one particle with a default-constructed payload.
Definition CD_ParticleSoA.H:962
double * weightColumn() noexcept
Raw weight column (double*).
Definition CD_ParticleSoA.H:1167
RealVect position(const std::size_t a_index) const noexcept
Position of particle i as a RealVect (by value, assembled from the scalar columns).
Definition CD_ParticleSoA.H:1195
double & weight(const std::size_t a_index) noexcept
Weight of particle i.
Definition CD_ParticleSoA.H:1229
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
double * positionColumn(const int a_dir) noexcept
Raw position component column dir (double*, for SIMD kernels).
Definition CD_ParticleSoA.H:1144
std::pair< std::size_t, std::size_t > cellRange(const std::size_t a_cell) const noexcept
Half-open particle index range [begin, end) owned by cell c (valid after sortByCell).
Definition CD_ParticleSoA.H:1546
void reserve(const std::size_t a_capacity)
Ensure capacity for at least a_capacity particles (reallocates + moves on growth).
Definition CD_ParticleSoAImplem.H:88
Abstract TimeStepper for the Ito-KMC-Poisson system of equations.
Definition CD_ItoKMCStepper.H:66
virtual void transferCoveredParticles(const SpeciesSubset a_speciesSubset, const EBRepresentation a_representation, const Real a_tolerance) noexcept
Transfer covered particles (i.e., particles inside the EB) from the ItoSolver bulk container to EB co...
Definition CD_ItoKMCStepperImplem.H:2596
virtual void setupCdr() noexcept
Set up the CDR solvers.
Definition CD_ItoKMCStepperImplem.H:530
virtual void getParticleStatistics(Real &a_avgParticles, Real &a_sigma, Real &a_minParticles, Real &a_maxParticles, int &a_minRank, int &a_maxRank)
Compute some particle statistics.
Definition CD_ItoKMCStepperImplem.H:1487
virtual void computePhysicsPlotVariables(EBAMRCellData &a_physicsPlotVars) noexcept
Compute physics plot variables.
Definition CD_ItoKMCStepperImplem.H:6414
virtual void computePhysicsDt() noexcept
Compute a physics-based maximum time step.
Definition CD_ItoKMCStepperImplem.H:5616
virtual void computeReactiveMeanEnergiesPerCell(EBAMRCellData &a_meanEnergies) noexcept
Compute the mean particle energy in all grid cells.
Definition CD_ItoKMCStepperImplem.H:3879
virtual void parseRuntimeOptions() noexcept override
Parse runtime configurable options.
Definition CD_ItoKMCStepperImplem.H:173
virtual void computeElectricField(EBAMRCellData &a_electricField, const phase::which_phase a_phase) const noexcept
Recompute the electric field onto the specified data holder.
Definition CD_ItoKMCStepperImplem.H:1970
virtual void removeCoveredParticles(const SpeciesSubset a_which, const EBRepresentation a_representation, const Real a_tolerance) noexcept
Remove covered particles (i.e., particles inside the EB)
Definition CD_ItoKMCStepperImplem.H:2477
virtual void setupRadiativeTransfer() noexcept
Set up the radiative transfer solver.
Definition CD_ItoKMCStepperImplem.H:549
virtual void registerRealms() noexcept override
Register realms used for the simulation.
Definition CD_ItoKMCStepperImplem.H:1634
virtual void loadBalanceBoxes(Vector< Vector< int > > &a_procs, Vector< Vector< Box > > &a_boxes, const std::string &a_realm, const Vector< DisjointBoxLayout > &a_grids, const int a_lmin, const int a_finestLevel) override
Load balance grid boxes for a specified realm.
Definition CD_ItoKMCStepperImplem.H:5945
virtual Vector< RefCountedPtr< ItoSolver > > getLoadBalanceSolvers() const noexcept
Get the solvers used for load balancing.
Definition CD_ItoKMCStepperImplem.H:5889
virtual void computeSpaceChargeDensity() noexcept
Compute the space charge. Calls the other version.
Definition CD_ItoKMCStepperImplem.H:1997
virtual void fillNeutralDensity() noexcept
Compute the neutral density on the mesh.
Definition CD_ItoKMCStepperImplem.H:1877
virtual void setVoltage(const std::function< Real(const Real a_time)> &a_voltage) noexcept
Set voltage used for the simulation.
Definition CD_ItoKMCStepperImplem.H:1865
virtual void advanceReactionNetwork(const Real a_dt) noexcept
Chemistry advance over time a_dt.
Definition CD_ItoKMCStepperImplem.H:4020
virtual Real getTime() const noexcept
Get current simulation time.
Definition CD_ItoKMCStepperImplem.H:1985
virtual Vector< long int > getCheckpointLoads(const std::string &a_realm, const int a_level) const override
Get computational loads to be checkpointed.
Definition CD_ItoKMCStepperImplem.H:6171
virtual void computeDummyPhysicsDt() noexcept
Special routine which performs a dummy KMC advance over a zero time step.
Definition CD_ItoKMCStepperImplem.H:5633
void reconcileParticles(const EBAMRCellData &a_newParticlesPerCell, const EBAMRCellData &a_oldParticlesPerCell, const EBAMRCellData &a_newPhotonsPerCell, const EBAMRCellData &a_cdrProduction, const EBAMRCellData &a_electricField) const noexcept
Reconcile particles. At the bottom, this will call the physics interface for particle reconciliation.
Definition CD_ItoKMCStepperImplem.H:4489
virtual int getNumberOfPlotVariables() const noexcept override
Get number of plot variables for the output file.
Definition CD_ItoKMCStepperImplem.H:985
virtual void remapParticles(const SpeciesSubset a_speciesSubset) noexcept
Remap a subset of ItoSolver particles.
Definition CD_ItoKMCStepperImplem.H:2720
virtual void parseVerbosity() noexcept
Parse chattiness.
Definition CD_ItoKMCStepperImplem.H:201
virtual void computeReactiveCdrParticlesPerCell(EBAMRCellData &a_ppc) noexcept
Compute the number of reactive particles per cell for the CDR solvers.
Definition CD_ItoKMCStepperImplem.H:3772
virtual void registerOperators() noexcept override
Register operators used for the simulation.
Definition CD_ItoKMCStepperImplem.H:1648
virtual void loadBalanceParticleRealm(Vector< Vector< int > > &a_procs, Vector< Vector< Box > > &a_boxes, const std::string a_realm, const Vector< DisjointBoxLayout > &a_grids, const int a_lmin, const int a_finestLevel) noexcept
Routine called by loadBalanceBoxes and used for particle-based load balancing.
Definition CD_ItoKMCStepperImplem.H:5967
virtual void averageDiffusionCoefficientsCellToFace() noexcept
Average cell-centered diffusion coefficient to faces.
Definition CD_ItoKMCStepperImplem.H:3582
virtual void parsePlotVariables() noexcept
Parse plot variables.
Definition CD_ItoKMCStepperImplem.H:272
virtual void parseSuperParticles() noexcept
Parse the super-particle merge cadence.
Definition CD_ItoKMCStepperImplem.H:308
virtual void writeData(LevelData< EBCellFAB > &a_output, int &a_comp, const EBAMRCellData &a_data, const std::string a_outputRealm, const int a_level, const bool a_interpToCentroids, const bool a_interpGhost) const noexcept
Write data to output. Convenience function.
Definition CD_ItoKMCStepperImplem.H:1143
virtual void computeDensityGradients() noexcept
Compute grad(phi) and phi for both CDR and Ito species and put the result on the fluid realm.
Definition CD_ItoKMCStepperImplem.H:2136
virtual void computeEdotJSource(const Real a_dt) noexcept
Compute the energy source term for the various plasma species.
Definition CD_ItoKMCStepperImplem.H:6220
virtual void parseCdrProducts() noexcept
Parse how the reaction network's CDR production reaches the mesh.
Definition CD_ItoKMCStepperImplem.H:244
virtual bool solvePoisson() noexcept
Solve the electrostatic problem.
Definition CD_ItoKMCStepperImplem.H:2253
virtual Real computeQminu() const noexcept
Compute negative charge.
Definition CD_ItoKMCStepperImplem.H:5754
virtual void multiplyCdrVelocitiesByMobilities() noexcept
Multiply CDR solver velocities by mobilities.
Definition CD_ItoKMCStepperImplem.H:3015
virtual void parseRedistributeCDR() noexcept
Parse CDR mass redistribution when assigning reactive products.
Definition CD_ItoKMCStepperImplem.H:230
virtual void computeCurrentDensity(EBAMRCellData &a_J) noexcept
Compute the current density.
Definition CD_ItoKMCStepperImplem.H:2199
virtual void writeNumberOfParticlesPerPatch(LevelData< EBCellFAB > &a_output, int &a_icomp, const std::string a_outputRealm, const int a_level) const noexcept
Write number of particles per patch to output holder.
Definition CD_ItoKMCStepperImplem.H:1210
virtual void parseTimeStepRestrictions() noexcept
Parse time step restrictions.
Definition CD_ItoKMCStepperImplem.H:401
virtual void fillSecondaryEmissionEB(const Real a_dt) noexcept
Resolve particle injection at EBs.
Definition CD_ItoKMCStepperImplem.H:5157
virtual void initialSigma() noexcept
Fill surface charge solver with initial data taken from the physics interface.
Definition CD_ItoKMCStepperImplem.H:800
virtual void intersectParticles(const SpeciesSubset a_speciesSubset, const bool a_delete, const std::function< void(ParticleSoA< ItoParticle > &, std::size_t)> a_nonDeletionModifier=[](ParticleSoA< ItoParticle > &, std::size_t) -> void { return;}) noexcept
Intersect a subset of the particles with the domain and embedded boundary.
Definition CD_ItoKMCStepperImplem.H:2292
ItoKMCStepper() noexcept
Default constructor. Sets default options.
Definition CD_ItoKMCStepperImplem.H:98
virtual void printStepReport() noexcept override
Print a step report. Used by Driver for user monitoring of simulation.
Definition CD_ItoKMCStepperImplem.H:1275
virtual void depositParticles(const SpeciesSubset a_speciesSubset) noexcept
Deposit a subset of the ItoSolver particles on the mesh.
Definition CD_ItoKMCStepperImplem.H:2835
virtual void setupSigma() noexcept
Set up the surface charge solver.
Definition CD_ItoKMCStepperImplem.H:586
virtual void setupSolvers() noexcept override
Set up solvers.
Definition CD_ItoKMCStepperImplem.H:495
virtual void parseOptions() noexcept
Parse options.
Definition CD_ItoKMCStepperImplem.H:152
virtual void advancePhotons(const Real a_dt) noexcept
Photon advancement routine.
Definition CD_ItoKMCStepperImplem.H:5810
virtual void preRegrid(const int a_lmin, const int a_oldFinestLevel) noexcept override
Perform pre-regrid operations - storing relevant data from the old grids.
Definition CD_ItoKMCStepperImplem.H:1700
virtual void depositCdrProducts(EBAMRCellData &a_cdrChange) noexcept
Deposit the CDR reaction products in m_cdrProducts and add the result to a per-cell change.
Definition CD_ItoKMCStepperImplem.H:4165
virtual void parseDualGrid() noexcept
Parse dual or single realm calculations.
Definition CD_ItoKMCStepperImplem.H:325
virtual void reconcilePhotoionization() noexcept
Reconcile the results from photoionization reactions.
Definition CD_ItoKMCStepperImplem.H:4895
virtual void loadBalanceFluidRealm(Vector< Vector< int > > &a_procs, Vector< Vector< Box > > &a_boxes, const std::string a_realm, const Vector< DisjointBoxLayout > &a_grids, const int a_lmin, const int a_finestLevel) noexcept
Routine called by loadBalanceBoxes and used for particle-based load balancing.
Definition CD_ItoKMCStepperImplem.H:6120
virtual Vector< std::string > getPlotVariableNames() const noexcept override
Get plot variable names.
Definition CD_ItoKMCStepperImplem.H:1038
virtual void sortPhotonsByCell(const McPhoto::WhichContainer a_which) noexcept
Sort photons by cells.
Definition CD_ItoKMCStepperImplem.H:5861
virtual Real computeQplus() const noexcept
Compute positive charge.
Definition CD_ItoKMCStepperImplem.H:5710
virtual void parseLoadBalance() noexcept
Parse load balancing.
Definition CD_ItoKMCStepperImplem.H:348
virtual void computeDriftVelocities() noexcept
Compute ItoSolver velocities.
Definition CD_ItoKMCStepperImplem.H:3041
virtual void setupPoisson() noexcept
Set up the electrostatic field solver.
Definition CD_ItoKMCStepperImplem.H:569
virtual void setupIto() noexcept
Set up the Ito particle solvers.
Definition CD_ItoKMCStepperImplem.H:511
virtual Real computeQsurf() const noexcept
Compute surface charge.
Definition CD_ItoKMCStepperImplem.H:5798
virtual Real computeDt() override
Compute a time step used for the advance method.
Definition CD_ItoKMCStepperImplem.H:1519
virtual void synchronizeSolverTimes(const int a_step, const Real a_time, const Real a_dt) noexcept override
Synchronize solver times for all the solvers.
Definition CD_ItoKMCStepperImplem.H:1256
virtual Real computeTotalCharge() const noexcept
Compute total charge.
Definition CD_ItoKMCStepperImplem.H:5690
virtual void resolveSecondaryEmissionEB(const Real a_dt) noexcept
Resolve secondary emission at the EB.
Definition CD_ItoKMCStepperImplem.H:5461
virtual void regrid(const int a_lmin, const int a_oldFinestLevel, const int a_newFinestLevel) noexcept override
Regrid methods – puts all data on the new mesh.
Definition CD_ItoKMCStepperImplem.H:1810
virtual void coarsenCDRSolvers(const bool a_interpGhosts) noexcept
Coarsen data for CDR solvers.
Definition CD_ItoKMCStepperImplem.H:5096
virtual void computeDiffusionCoefficients() noexcept
Compute mesh-based diffusion coefficients for LFA coupling.
Definition CD_ItoKMCStepperImplem.H:3299
virtual void allocateInternals() noexcept
Allocate "internal" storage.
Definition CD_ItoKMCStepperImplem.H:621
virtual bool loadBalanceThisRealm(const std::string &a_realm) const override
Load balancing query for a specified realm. If this returns true for a_realm, load balancing routines...
Definition CD_ItoKMCStepperImplem.H:5924
virtual Real computeMaxReducedElectricField(const phase::which_phase a_phase) const noexcept
Compute the maximum electric field (norm)
Definition CD_ItoKMCStepperImplem.H:1934
virtual void setCdrVelocityFunctions() noexcept
Set the Cdr velocities to be sgn(charge) * E.
Definition CD_ItoKMCStepperImplem.H:2981
virtual void postRegrid() noexcept override
Perform post-regrid operations.
Definition CD_ItoKMCStepperImplem.H:1852
virtual Real computeRelaxationTime() noexcept
Compute the dielectric relaxation time.
Definition CD_ItoKMCStepperImplem.H:2218
virtual void parseParametersEB() noexcept
Parse parameters related to how we treat particle-EB interaction.
Definition CD_ItoKMCStepperImplem.H:479
virtual void initialData() noexcept override
Fill solvers with initial data.
Definition CD_ItoKMCStepperImplem.H:763
virtual void postPlot() noexcept override
Perform post-plot operations.
Definition CD_ItoKMCStepperImplem.H:1688
virtual ~ItoKMCStepper() noexcept
Destructor.
Definition CD_ItoKMCStepperImplem.H:145
virtual void getMaxMinRelativeItoDensity(Real &a_maxDensity, Real &a_minDensity, std::string &a_maxSolver, std::string &a_minSolver) const noexcept
Get maximum density of the Ito species (only for charged species)
Definition CD_ItoKMCStepperImplem.H:1392
virtual void computeReactiveItoParticlesPerCell(EBAMRCellData &a_ppc) noexcept
Compute the number of reactive particles per cell.
Definition CD_ItoKMCStepperImplem.H:3648
virtual bool needSecondaryEmissionEB() const noexcept
Whether anything can be emitted from the EB this step, answered globally.
Definition CD_ItoKMCStepperImplem.H:5126
virtual void allocate() noexcept override
Allocate storage for solvers.
Definition CD_ItoKMCStepperImplem.H:603
virtual void parseExitOnFailure() noexcept
Parse exit on failure.
Definition CD_ItoKMCStepperImplem.H:216
virtual void computeConductivityCell(EBAMRCellData &a_conductivity) noexcept
Compute the cell-centered conductiivty.
Definition CD_ItoKMCStepperImplem.H:2068
virtual void postCheckpointPoisson() noexcept
Do some post-checkpoint operations for the electrostatic part.
Definition CD_ItoKMCStepperImplem.H:868
virtual void reconcileCdrDensities(const EBAMRCellData &a_cdrChange, const Real a_dt) noexcept
Reconcile the CDR densities after the reaction network.
Definition CD_ItoKMCStepperImplem.H:4953
virtual void prePlot() noexcept override
Perform pre-plot operations.
Definition CD_ItoKMCStepperImplem.H:1667
virtual void postInitialize() noexcept override
Post-initialization operations. Default does nothing.
Definition CD_ItoKMCStepperImplem.H:753
virtual void postCheckpointSetup() noexcept override
Perform post-checkpoint operations.
Definition CD_ItoKMCStepperImplem.H:849
virtual void getMaxMinRelativeCDRDensity(Real &a_maxDensity, Real &a_minDensity, std::string &a_maxSolver, std::string &a_minSolver) const noexcept
Get maximum density of the CDR species (only for charged species)
Definition CD_ItoKMCStepperImplem.H:1440
virtual void writePlotData(LevelData< EBCellFAB > &a_output, int &a_icomp, const std::string &a_outputRealm, const int a_level) const noexcept override
Write plot data to output holder.
Definition CD_ItoKMCStepperImplem.H:1089
virtual void computeMobilities() noexcept
Compute mesh-based mobilities for LFA coupling.
Definition CD_ItoKMCStepperImplem.H:3066
virtual void setItoVelocityFunctions() noexcept
Set the Ito velocity functions. This is sgn(charge) * E.
Definition CD_ItoKMCStepperImplem.H:2950
virtual void sortPhotonsByPatch(const McPhoto::WhichContainer a_which) noexcept
Sort photons by patch.
Definition CD_ItoKMCStepperImplem.H:5875
virtual void getPhysicalParticlesPerCell(EBAMRCellData &a_ppc) const noexcept
Get the physical number of particles per cell.
Definition CD_ItoKMCStepperImplem.H:3626
static const std::string Primal
Identifier for perimal realm.
Definition CD_Realm.H:44
Factory class for RtLayout.
Definition CD_RtLayout.H:278
RefCountedPtr< RtLayout< T > > newLayout(const Vector< RefCountedPtr< RtSpecies > > &a_species) const
Get a new Layout. This will cast S to a specific class (T)
Definition CD_RtLayoutImplem.H:458
Iterator class for RtLayout.
Definition CD_RtIterator.H:25
virtual bool ok()
Check if we can cycle further through the solvers.
Definition CD_RtIteratorImplem.H:65
Surface ODE solver.
Definition CD_SurfaceODESolver.H:29
virtual Vector< long int > getCheckpointLoads(const std::string &a_realm, int a_level) const
Get computational loads to be checkpointed.
Definition CD_TimeStepper.cpp:78
Class which is used for run-time monitoring of events.
Definition CD_Timer.H:32
void startEvent(const std::string &a_event) noexcept
Start an event.
Definition CD_TimerImplem.H:60
void eventReport(std::ostream &a_outputStream, const bool a_localReportOnly=false) const noexcept
Print all timed events to cout.
Definition CD_TimerImplem.H:170
void stopEvent(const std::string &a_event) noexcept
Stop an event.
Definition CD_TimerImplem.H:89
ALWAYS_INLINE void loop(const Box &a_computeBox, Functor &&kernel)
Launch a C++ kernel over a regular grid with compile-time per-dimension strides.
Definition CD_BoxLoopsImplem.H:39
std::string numberFmt(long long n, char a_sep=',') noexcept
Number formatting method – writes big numbers using an input separator. E.g. the number 123456 is wri...
Definition CD_DischargeIO.cpp:37
RealVect position(Location::Cell a_location, const VolIndex &a_vof, const EBISBox &a_ebisbox, const Real &a_dx)
Compute the position (ignoring the "origin) of a Vof.
Definition CD_LocationImplem.H:21
std::pair< Real, int > maxRank(const Real &a_val) noexcept
Get the maximum value and the rank having the maximum value.
Definition CD_ParallelOpsImplem.H:294
Real average(const Real &a_val) noexcept
Compute the average (across MPI ranks) of the input value.
Definition CD_ParallelOpsImplem.H:521
Real standardDeviation(const Real &a_value) noexcept
Compute the standard deviation of the input value.
Definition CD_ParallelOpsImplem.H:533
std::pair< Real, int > minRank(const Real &a_val) noexcept
Get the minimum value and the rank having the minimum value.
Definition CD_ParallelOpsImplem.H:324
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354
ALWAYS_INLINE void loop(const ParticleSoA< P, Traits > &a_soa, Functor &&a_kernel)
Launch a kernel over every particle in a ParticleSoA, decorating the loop with CD_PRAGMA_SIMD.
Definition CD_ParticleLoops.H:87
constexpr Real eps0
Permittivity of free space.
Definition CD_Units.H:30
constexpr Real Qe
Elementary charge.
Definition CD_Units.H:35
constexpr Real c
Speed of light.
Definition CD_Units.H:40
which_phase
Enumeration of supported phases.
Definition CD_MultiFluidIndexSpace.H:38
@ gas
Gas phase.
Definition CD_MultiFluidIndexSpace.H:39
SoA payload for the transient particles used by ItoKMCStepper::computeEdotJSource.
Definition CD_ItoKMCFieldParticle.H:29
ParticleReal phiA
Electrostatic potential at position A.
Definition CD_ItoKMCFieldParticle.H:30
ParticleReal phiB
Electrostatic potential at position B.
Definition CD_ItoKMCFieldParticle.H:31
double x0_z
Alternate (other) position, z-component (double: position-like).
Definition CD_ItoKMCFieldParticle.H:36
double x0_y
Alternate (other) position, y-component (double: position-like).
Definition CD_ItoKMCFieldParticle.H:34
double x0_x
Alternate (other) position, x-component (double: position-like).
Definition CD_ItoKMCFieldParticle.H:33