chombo-discharge
Loading...
Searching...
No Matches
CD_ItoKMCGodunovStepperImplem.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_ITOKMCGODUNOVSTEPPERIMPLEM_H
14#define CD_ITOKMCGODUNOVSTEPPERIMPLEM_H
15
16// Chombo includes
17#include <ParmParse.H>
18
19// Our includes
21#include <CD_Timer.H>
22#include <CD_ParallelOps.H>
23#include <CD_DataOps.H>
24#include <CD_ParticleLoops.H>
25#include <CD_ParticleOps.H>
26#include <CD_Units.H>
27#include <CD_Photon.H>
28#include <CD_DischargeIO.H>
29#include <CD_NamespaceHeader.H>
30
31using namespace Physics::ItoKMC;
32
33namespace {
34// Deposit an SoA point-particle container's weight onto a_phi through the Ito solver's own deposition routine, so
35// that the point particles land on the mesh in the same normalization as the solver's own particles. Going through
36// the solver -- rather than calling AmrMesh::depositWeight directly -- is what picks up the solver's cut-cell
37// deposition flag and its redistribution, and what guarantees the realm and phase are the solver's own.
38//
39// No coarsenAndFillGhosts() here: every caller sums these deposits into rho or the cell conductivity and then
40// coarsens and fills ghost cells on that sum, so doing it per species would only produce values that are
41// immediately overwritten.
42inline void
43depositPointParticlesLikeSolver(const RefCountedPtr<ItoSolver>& a_solver,
44 EBAMRCellData& a_phi,
45 const ParticleContainer<NoPayload>& a_particles) noexcept
46{
47 a_solver->depositWeight(a_phi, a_particles, a_solver->getDeposition(), a_solver->getCoarseFineDeposition());
48}
49
50} // namespace
51
52template <typename I, typename C, typename R, typename F>
53ItoKMCGodunovStepper<I, C, R, F>::ItoKMCGodunovStepper(RefCountedPtr<ItoKMCPhysics>& a_physics, bool a_parseOptions)
54 : ItoKMCStepper<I, C, R, F>(a_physics)
55{
56 CH_TIME("ItoKMCGodunovStepper::ItoKMCGodunovStepper");
58 this->m_name = "ItoKMCGodunovStepper";
59 this->m_prevDt = 0.0;
60 this->m_writeCheckpointParticles = false;
61 this->m_readCheckpointParticles = false;
62 this->m_extendConductivityEB = false;
64 this->m_rhoDaggerHop = true;
66 this->m_prevDt = 0.0;
67 this->m_maxFieldAbort = std::numeric_limits<Real>::max();
68
69 if (a_parseOptions) {
70 this->parseOptions();
71 }
72}
73
74template <typename I, typename C, typename R, typename F>
76{
77 CH_TIME("ItoKMCGodunovStepper::~ItoKMCGodunovStepper");
78 if (this->m_verbosity > 5) {
79 pout() << "ItoKMCGodunovStepper::~ItoKMCGodunovStepper" << endl;
80 }
81}
82
83template <typename I, typename C, typename R, typename F>
84void
86{
87 CH_TIME("ItoKMCGodunovStepper::registerOperators");
88 if (this->m_verbosity > 5) {
89 pout() << "ItoKMCGodunovStepper::registerOperators" << endl;
90 }
91
94 // Register this because we must be able to deposit particles inside dielectrics (due to particle diffusion across the
95 // EB).
96 (this->m_amr)->registerOperator(s_particle_mesh, this->m_particleRealm, phase::solid);
97}
98
99template <typename I, typename C, typename R, typename F>
100void
102{
103 CH_TIME("ItoKMCGodunovStepper::allocate");
104 if (this->m_verbosity > 5) {
105 pout() << "ItoKMCGodunovStepper::allocate" << endl;
106 }
107
109
110 // Now allocate for the conductivity particles and rho^dagger particles. This is only done in the 'allocate' routine
111 // and not in 'allocateInternals' because that would discard the particles during regrids. That has definitely never
112 // happen, and there's no way I've spent countless hours tracking down such a bug.
113 const int numItoSpecies = this->m_physics->getNumItoSpecies();
114
115 m_conductivityParticles.resize(numItoSpecies);
116 m_irregularParticles.resize(numItoSpecies);
117 m_rhoDaggerParticles.resize(numItoSpecies);
118
119 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
120 const int idx = solverIt.index();
121
122 m_conductivityParticles[idx] = RefCountedPtr<ParticleContainer<NoPayload>>(new ParticleContainer<NoPayload>());
123 m_irregularParticles[idx] = RefCountedPtr<ParticleContainer<NoPayload>>(new ParticleContainer<NoPayload>());
124 m_rhoDaggerParticles[idx] = RefCountedPtr<ParticleContainer<NoPayload>>(new ParticleContainer<NoPayload>());
125
126 (this->m_amr)->allocate(*m_conductivityParticles[idx], this->m_particleRealm);
127 (this->m_amr)->allocate(*m_irregularParticles[idx], this->m_particleRealm);
128 (this->m_amr)->allocate(*m_rhoDaggerParticles[idx], this->m_particleRealm);
129 }
130}
131
132template <typename I, typename C, typename R, typename F>
133void
135{
136 CH_TIME("ItoKMCGodunovStepper::allocateInternals");
137 if (this->m_verbosity > 5) {
138 pout() << this->m_name + "::allocateInternals" << endl;
139 }
140
142
143 const int numCdrSpecies = this->m_physics->getNumCdrSpecies();
144
145 m_cdrDivD.resize(numCdrSpecies);
146 for (int i = 0; i < numCdrSpecies; i++) {
147 this->m_amr->allocate(m_cdrDivD[i], this->m_fluidRealm, this->m_plasmaPhase, 1);
148 }
149
150 this->m_amr->allocate(m_semiImplicitRhoCDR, this->m_fluidRealm, this->m_plasmaPhase, 1);
151 this->m_amr->allocate(m_semiImplicitConductivityCDR, this->m_fluidRealm, this->m_plasmaPhase, 1);
152
153 // AmrMesh::allocate does not initialize. Both fields are consumed by regrid(), which may run before an
154 // advance has filled them, so give them a defined value here. Later regrids overwrite them through
155 // interpToNewGrids.
156 DataOps::setValue(m_semiImplicitRhoCDR, 0.0);
157 DataOps::setValue(m_semiImplicitConductivityCDR, 0.0);
158
159 // Holds the field the chemistry is evaluated at. This one needs no initialization -- advance() overwrites
160 // it from m_electricFieldFluid before anything reads it, and nothing else consumes it.
161 this->m_amr->allocate(m_reactiveElectricField, this->m_fluidRealm, this->m_plasmaPhase, SpaceDim);
162
163 // Accumulator for the per-species charge-weighted sums, so the cross-realm copy runs once rather than
164 // once per species. Zeroed by its users before they accumulate into it.
165 this->m_amr->allocate(m_particleScratchSum, this->m_particleRealm, this->m_plasmaPhase, 1);
166
167 // Solid-phase buffers for the space charge that gas-side particles carried into the dielectrics. These
168 // exist only when there is a dielectric to carry charge into; computeSemiImplicitRho() asks the same
169 // question before it touches them, and used to allocate them on every time step.
170 const RefCountedPtr<MultiFluidIndexSpace>& mfis = (this->m_computationalGeometry)->getMfIndexSpace();
171 const Vector<Dielectric>& dielectrics = (this->m_computationalGeometry)->getDielectrics();
172
173 if ((mfis->numPhases() > 1) && (dielectrics.size() > 0)) {
174 this->m_amr->allocate(m_particleScratchSolid, this->m_particleRealm, phase::solid, 1);
175 this->m_amr->allocate(m_particleScratchSumSolid, this->m_particleRealm, phase::solid, 1);
176 this->m_amr->allocate(m_fluidScratchSolid, this->m_fluidRealm, phase::solid, 1);
177 }
178}
179
180template <typename I, typename C, typename R, typename F>
181void
183{
184 CH_TIME("ItoKMCGodunovStepper::barrier");
185 if (this->m_verbosity > 5) {
186 pout() << this->m_name + "::barrier" << endl;
187 }
188
189 if ((this->m_profile)) {
191 }
192}
193
194template <typename I, typename C, typename R, typename F>
195void
197{
198 CH_TIME("ItoKMCGodunovStepper::parseOptions");
199 if (this->m_verbosity > 5) {
200 pout() << this->m_name + "::parseOptions" << endl;
201 }
202
204
205 this->parseAlgorithm();
206 this->parseFiltering();
207 this->parseCheckpointParticles();
208 this->parseSecondaryEmissionSpecification();
209 this->parseDiffusiveDeposit();
210 this->parseRhoDaggerHop();
211 this->parseReactiveFieldCentering();
212}
213
214template <typename I, typename C, typename R, typename F>
215void
217{
218 CH_TIME("ItoKMCGodunovStepper::parseRuntimeOptions");
219 if (this->m_verbosity > 5) {
220 pout() << this->m_name + "::parseRuntimeOptions" << endl;
221 }
222
224
225 this->parseAlgorithm();
226 this->parseFiltering();
227 this->parseCheckpointParticles();
228 this->parseSecondaryEmissionSpecification();
229 this->parseDiffusiveDeposit();
230 this->parseRhoDaggerHop();
231 this->parseReactiveFieldCentering();
232}
233
234template <typename I, typename C, typename R, typename F>
235void
237{
238 CH_TIME("ItoKMCGodunovStepper::parseAlgorithm");
239 if (this->m_verbosity > 5) {
240 pout() << this->m_name + "::parseAlgorithm" << endl;
241 }
242
243 ParmParse pp(this->m_name.c_str());
244 std::string str;
245
246 pp.get("extend_conductivity", m_extendConductivityEB);
247 pp.get("algorithm", str);
248 pp.get("abort_max_field", m_maxFieldAbort);
249
250 // Get algorithm
251 if (str == "euler_maruyama") {
252 m_algorithm = WhichAlgorithm::EulerMaruyama;
253 }
254 else {
255 MayDay::Abort("ItoKMCGodunovStepper::parseAlgorithm - unknown algorithm requested");
256 }
257}
258
259template <typename I, typename C, typename R, typename F>
260void
262{
263 CH_TIME("ItoKMCGodunovStepper::parseFiltering");
264 if (this->m_verbosity > 5) {
265 pout() << this->m_name + "::parseFiltering" << endl;
266 }
267
268 ParmParse pp(this->m_name.c_str());
269 std::string str;
270
271 m_rhoFilterNum = -1;
272 m_rhoFilterMaxStride = 1;
273 m_rhoFilterAlpha = 0.5;
274
275 m_condFilterNum = -1;
276 m_condFilterMaxStride = 1;
277 m_condFilterAlpha = 0.5;
278
279 pp.get("rho_filter_num", m_rhoFilterNum);
280 pp.get("rho_filter_max_stride", m_rhoFilterMaxStride);
281 pp.get("rho_filter_alpha", m_rhoFilterAlpha);
282
283 pp.get("cond_filter_num", m_condFilterNum);
284 pp.get("cond_filter_max_stride", m_condFilterMaxStride);
285 pp.get("cond_filter_alpha", m_condFilterAlpha);
286
287 if (m_rhoFilterAlpha <= 0.0 || m_rhoFilterAlpha >= 1.0) {
288 MayDay::Abort("ItoKMCGodunovStepper::parseFiltering -- cannot have alpha <= 0 or alpha >= 1 for rho_filter");
289 }
290 if (m_condFilterAlpha <= 0.0 || m_condFilterAlpha >= 1.0) {
291 MayDay::Abort("ItoKMCGodunovStepper::parseFiltering -- cannot have alpha <= 0 or alpha >= 1 for cond_filter");
292 }
293}
294
295template <typename I, typename C, typename R, typename F>
296void
298{
299 CH_TIME("ItoKMCGodunovStepper::parseCheckpointParticles");
300 if (this->m_verbosity > 5) {
301 pout() << this->m_name + "::parseCheckpointParticles" << endl;
302 }
303
304 ParmParse pp(this->m_name.c_str());
305
306 pp.query("checkpoint_particles", m_writeCheckpointParticles);
307}
308
309template <typename I, typename C, typename R, typename F>
310void
312{
313 CH_TIME("ItoKMCGodunovStepper::parseSecondaryEmissionSpecifiation");
314 if (this->m_verbosity > 5) {
315 pout() << this->m_name + "::parseSecondaryEmissionSpecification" << endl;
316 }
317
318 ParmParse pp(this->m_name.c_str());
319
320 std::string str;
321
322 pp.query("secondary_emission", str);
323
324 if (str == "before_reactions") {
325 m_emitSecondaryParticlesBeforeReactions = true;
326 }
327 else if (str == "after_reactions") {
328 m_emitSecondaryParticlesBeforeReactions = false;
329 }
330 else {
331 std::string err;
332
333 err = "ItoKMCGodunovStepper::parseSecondaryEmissionSpecification - expected 'before_reactions' or 'after_reactions'";
334 err += "but got" + str;
335
336 MayDay::Abort(err.c_str());
337 }
338}
339
340template <typename I, typename C, typename R, typename F>
341RealVect
343 const RealVect& a_disp,
344 const Real a_fOld,
345 const Real a_dx,
346 const EBIntersection a_intersectionAlg,
347 const Real a_bisectStep) const noexcept
348{
349 // One reflection places the particle in the fluid unless the surface curves back on it, so the second attempt is
350 // for concave corners and thin features. There is no third: the fallback is always available and always safe.
351 constexpr int numAttempts = 2;
352
353 // Ray-cast collision tolerance, relative to dx. Same value the solvers use for their own intersection tests.
354 constexpr Real raycastTolerance = 1.E-3;
355
356 const RefCountedPtr<BaseIF>& baseif = (this->m_amr)->getBaseImplicitFunction(this->m_plasmaPhase);
357
358 // Which sign of the implicit function is the fluid, taken from a position known to be in it. Nothing here assumes
359 // which side the implicit function calls positive.
360 const Real fluidSign = (a_fOld > 0.0) ? 1.0 : -1.0;
361 const Real h = 1.E-2 * a_dx;
362
363 const auto inFluid = [&](const RealVect& a_x) -> bool {
364 return fluidSign * baseif->value(a_x) > 0.0;
365 };
366
367 // The crossing point along [a_pos, a_end], located with whichever algorithm the owning solver was configured with
368 // so that reflection and the absorption test later in the advance agree on where the surface is.
369 const auto findCrossing = [&](const RealVect& a_end, Real& a_s) -> bool {
370 switch (a_intersectionAlg) {
371 case EBIntersection::Bisection: {
372 return ParticleOps::ebIntersectionBisect(baseif, a_pos, a_end, a_bisectStep, a_s);
373 }
374 case EBIntersection::Raycast: {
375 return ParticleOps::ebIntersectionRaycast(baseif, a_pos, a_end, raycastTolerance * a_dx, a_s);
376 }
377 default: {
378 MayDay::Abort("ItoKMCGodunovStepper::reflectDiffusionHop - unsupported EB intersection requested");
379
380 return false;
381 }
382 }
383 };
384
385 RealVect end = a_pos + a_disp;
386
387 for (int attempt = 0; attempt < numAttempts; attempt++) {
388 Real s = std::numeric_limits<Real>::max();
389
390 // Always bracketed from a_pos rather than from the previous crossing point. a_pos is in the fluid and the current
391 // end is not, so the bracket is valid on every attempt, and no search is started from a point on the surface --
392 // where both intersection routines would report a crossing at zero path length and reflect about it forever.
393 if (!findCrossing(end, s)) {
394
395 // No crossing on the segment. Either the reflected point is reachable without one, or the routine could not
396 // resolve the feature that put it inside; the implicit function decides which.
397 return inFluid(end) ? (end - a_pos) : RealVect::Zero;
398 }
399
400 const RealVect cross = a_pos + s * (end - a_pos);
401
402 // Surface normal from the gradient of the implicit function at the crossing point.
403 RealVect grad = RealVect::Zero;
404
405 for (int dir = 0; dir < SpaceDim; dir++) {
406 RealVect lo = cross;
407 RealVect hi = cross;
408
409 lo[dir] -= h;
410 hi[dir] += h;
411
412 grad[dir] = (baseif->value(hi) - baseif->value(lo)) / (2.0 * h);
413 }
414
415 const Real gradLen = grad.vectorLength();
416
417 if (gradLen <= 0.0) {
418 return RealVect::Zero;
419 }
420
421 const RealVect n = grad / gradLen;
422
423 // Mirror the part of the path that lies beyond the surface back across it, so a point that would have landed at
424 // depth d inside comes to rest at depth d outside.
425 const RealVect overshoot = end - cross;
426
427 end = cross + overshoot - 2.0 * overshoot.dotProduct(n) * n;
428
429 if (inFluid(end)) {
430 return end - a_pos;
431 }
432 }
433
434 return RealVect::Zero;
435}
436
437template <typename I, typename C, typename R, typename F>
438void
440{
441 CH_TIME("ItoKMCGodunovStepper::parseDiffusiveDeposit");
442 if (this->m_verbosity > 5) {
443 pout() << this->m_name + "::parseDiffusiveDeposit" << endl;
444 }
445
446 ParmParse pp(this->m_name.c_str());
448 std::string str;
449
450 pp.get("diffusive_deposit", str);
451
452 if (str == "inside") {
453 m_diffusiveDeposit = DiffusiveDeposit::Inside;
454 }
455 else if (str == "cancel") {
456 m_diffusiveDeposit = DiffusiveDeposit::Cancel;
457 }
458 else if (str == "reflect_rho") {
459 m_diffusiveDeposit = DiffusiveDeposit::ReflectRho;
460 }
461 else if (str == "reflect_ito") {
462 m_diffusiveDeposit = DiffusiveDeposit::ReflectIto;
463 }
464 else {
465 const std::string err = "ItoKMCGodunovStepper::parseDiffusiveDeposit - expected 'inside', 'cancel', "
466 "'reflect_rho' or 'reflect_ito' but got '" +
467 str + "'";
468
469 MayDay::Abort(err.c_str());
470 }
471}
473template <typename I, typename C, typename R, typename F>
474void
476{
477 CH_TIME("ItoKMCGodunovStepper::parseRhoDaggerHop");
478 if (this->m_verbosity > 5) {
479 pout() << this->m_name + "::parseRhoDaggerHop" << endl;
480 }
482 ParmParse pp(this->m_name.c_str());
483
484 pp.get("rho_dagger_hop", m_rhoDaggerHop);
485}
486
487template <typename I, typename C, typename R, typename F>
488void
490{
491 CH_TIME("ItoKMCGodunovStepper::parseReactiveFieldCentering");
492 if (this->m_verbosity > 5) {
493 pout() << this->m_name + "::parseReactiveFieldCentering" << endl;
494 }
495
496 ParmParse pp(this->m_name.c_str());
497
498 pp.get("reactive_E_centering", m_reactiveFieldCentering);
499
500 // Anything outside [0,1] extrapolates past the two fields that were actually solved for, which has no
501 // physical justification.
502 if (m_reactiveFieldCentering < 0.0 || m_reactiveFieldCentering > 1.0) {
503 MayDay::Abort("ItoKMCGodunovStepper::parseReactiveFieldCentering -- 'reactive_E_centering' must lie in [0,1]");
504 }
505}
506
507template <typename I, typename C, typename R, typename F>
508Real
510{
511 CH_TIME("ItoKMCGodunovStepper::computeDt");
512 if (this->m_verbosity > 5) {
513 pout() << this->m_name + "::computeDt" << endl;
515
517
518 if ((this->m_maxReducedField > m_maxFieldAbort) && (m_maxFieldAbort > 0.0)) {
519 pout() << this->m_name + " stopping because maximum field is too high (" << this->m_maxReducedField << ")" << endl;
520
521 this->m_keepGoing = false;
522 }
523
524 return dt;
526
527template <typename I, typename C, typename R, typename F>
528Real
530{
531 CH_TIME("ItoKMCGodunovStepper::advance");
532 if (this->m_verbosity > 5) {
533 pout() << this->m_name + "::advance" << endl;
534 }
535
536 // Special flag for telling the class that we have the necessary things in place for doing a regrid. This is
537 // an if-but-maybe situation where the user chose not to checkpoint the particles we need for regrids, yet tries
538 // to restart a simulation and regrid without all the prerequisites being in place. This flag is set to true
539 // because these requirements are checked during the regrid routine.
540 m_canRegridOnRestart = true;
541
542 m_timer = Timer("ItoKMCGodunovStepper::advance");
543
544 // Previous time step is needed when regridding.
545 this->m_prevDt = a_dt;
547 // Store E^k -- the field the step starts from. The chemistry is later evaluated at an interpolant between
548 // this field and the one that comes out of the semi-implicit transport step. At theta == 1 that interpolant
549 // is E^(k+1) alone, so the buffer would only ever hold a copy that the blend below overwrites in full;
550 // advanceReactionNetwork is handed m_electricFieldFluid directly in that case and this is skipped.
551 m_timer.startEvent("Store E^k");
552 if (m_reactiveFieldCentering < 1.0) {
553 DataOps::copy(m_reactiveElectricField, this->m_electricFieldFluid);
554 }
555 m_timer.stopEvent("Store E^k");
556
557 // ====== BEGIN TRANSPORT STEP ======
558 // Semi-implicitly advance the particles and the field.
559 switch (m_algorithm) {
560 case WhichAlgorithm::EulerMaruyama: {
561 this->advanceEulerMaruyama(a_dt);
562
563 break;
564 }
565 default: {
566 MayDay::Abort("ItoKMCGodunovStepper::advance - logic bust");
567
568 break;
569 }
570 }
571
572 // Do intersection test and remove particles that struck the EB or domain. Transfer them to appropriate containers.
573 // Then recompute the number of particles per cell.
574 this->barrier();
575 m_timer.startEvent("EB/Particle intersection");
576 if (m_extendConductivityEB) {
578 // clang-format off
579 // TLDR: This hook does a special intersection routine where instead of transferring the particles that were intersected, they
580 // are put in a separate data container. We want to do this in order to reduce artificial gradients in the particle
581 // densities near the EB. The way we do this is that we flag the particles during the intersection; particles that are flagged
582 // are transferred to a container which is added to the conductivity-related particles. The particles are later removed.
583 // clang-format on
584 const std::function<void(ParticleSoA<ItoParticle>&, std::size_t)> setFlag = [](ParticleSoA<ItoParticle>& leaf,
585 const std::size_t i) -> void {
586 leaf.template get<&ItoParticle::scratch>(i) = 1.0;
587 };
588 const std::function<void(ParticleSoA<ItoParticle>&, std::size_t)> nonDeletionModifier =
589 [](ParticleSoA<ItoParticle>& leaf, const std::size_t i) -> void {
590 leaf.template get<&ItoParticle::scratch>(i) = -1.0;
591 };
592
593 // Set flag for identifying which particles were intersected by not removed. Only the subset that the
594 // intersection below runs over can ever be flagged, so a stationary species' population is not walked
595 // here -- nor in the deletion sweep at the end of this block, which uses the same subset.
596 for (auto it = this->m_ito->iterator(); it.ok(); ++it) {
597 const RefCountedPtr<ItoSolver>& solver = it();
598
599 if (solver->isMobile() || solver->isDiffusive()) {
600 ParticleOps::setData(solver->getParticles(ItoSolver::WhichContainer::Bulk), setFlag);
601 }
602 }
603
604 // Intersect particles, but don't remove them.
605 const bool deleteParticles = false;
606 this->intersectParticles(SpeciesSubset::AllMobileOrDiffusive, deleteParticles, nonDeletionModifier);
607
608 // Particles that were not removed are copied to a separate data container.
609 for (auto it = this->m_ito->iterator(); it.ok(); ++it) {
610 const RefCountedPtr<ItoSolver>& solver = it();
611 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
612
613 const int idx = it.index();
614 const int Z = species->getChargeNumber();
615
616 ParticleContainer<NoPayload>& irregParticles = *m_irregularParticles[idx];
617 ParticleContainer<ItoParticle>& ebParticles = solver->getParticles(ItoSolver::WhichContainer::EB);
618 ParticleContainer<ItoParticle>& bulkParticles = it()->getParticles(ItoSolver::WhichContainer::Bulk);
619
620 irregParticles.clearParticles();
621
622 if (Z != 0 && solver->isMobile()) {
623 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
624 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
625 const DataIterator& dit = dbl.dataIterator();
626
627 const int nbox = dit.size();
628
629#pragma omp parallel for schedule(runtime)
630 for (int mybox = 0; mybox < nbox; mybox++) {
631 const DataIndex& din = dit[mybox];
632
633 ParticleSoA<NoPayload>& pointParticles = irregParticles[lvl][din];
634 const ParticleSoA<ItoParticle>& leaf = bulkParticles[lvl][din];
635
636 for (std::size_t i = 0; i < leaf.size(); i++) {
637 if (leaf.template get<&ItoParticle::scratch>(i) < 0.0) {
638 const RealVect pos = leaf.position(i);
639 const Real weight = leaf.weight(i);
640 const Real mobility = leaf.template get<&ItoParticle::mobility>(i);
641
642 pointParticles.append(pos, weight * mobility);
643 }
644 }
646 }
647 }
648 }
649
650 // Finally, delete the original particles that were flagged (scratch < 0) via swap-and-pop on the SoA leaves.
651 for (auto it = this->m_ito->iterator(); it.ok(); ++it) {
652 const RefCountedPtr<ItoSolver>& solver = it();
653
654 if (!(solver->isMobile() || solver->isDiffusive())) {
655 continue;
656 }
657
658 ParticleContainer<ItoParticle>& bulkParticles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
659
660 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
661 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
662 const DataIterator& dit = dbl.dataIterator();
663
664 const int nbox = dit.size();
665
666#pragma omp parallel for schedule(runtime)
667 for (int mybox = 0; mybox < nbox; mybox++) {
668 const DataIndex& din = dit[mybox];
669
670 ParticleSoA<ItoParticle>& leaf = bulkParticles[lvl][din];
671
672 std::size_t i = 0;
673 while (i < leaf.size()) {
674 if (leaf.template get<&ItoParticle::scratch>(i) < 0.0) {
675 leaf.remove(i);
676 }
677 else {
678 i++;
679 }
680 }
681 }
682 }
683 }
684 }
685 else {
686 const bool deleteParticles = true;
687
688 this->intersectParticles(SpeciesSubset::AllMobileOrDiffusive, deleteParticles);
689 }
690
691 // The intersection tests above may not have caught all particles -- do a cleanup sweep where particles on the wrong
692 // side of the EB are put on the EB.
693 //
694 // Deliberately over every species, not just the ones that moved: the chemistry draws new particles at
695 // random positions within a cell, so a stationary species can acquire a particle on the solid side of a
696 // cut cell without ever taking a step. This sweep is the only thing that catches those -- the covered-cell
697 // removal below runs on AllMobileOrDiffusive.
698 for (auto it = this->m_ito->iterator(); it.ok(); ++it) {
699 const RefCountedPtr<ItoSolver>& solver = it();
700
701 ParticleContainer<ItoParticle>& ebParticles = solver->getParticles(ItoSolver::WhichContainer::EB);
702 ParticleContainer<ItoParticle>& bulkParticles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
703
704 this->m_amr->transferIrregularParticles(ebParticles, bulkParticles, this->m_plasmaPhase);
705 }
706 m_timer.stopEvent("EB/Particle intersection");
707 // ====== END TRANSPORT STEP ======
708
709 // Photon transport
710 this->barrier();
711 m_timer.startEvent("Photon transport");
712 this->advancePhotons(a_dt);
713 m_timer.stopEvent("Photon transport");
714
715 // Compute the gradients of the various species densities - this is used in the KMC kernels.
716 if ((this->m_physics)->needGradients()) {
717 m_timer.startEvent("Gradient calculation");
718
719 // Only the species computeDensityGradients() actually differentiates. It skips the rest and zeroes
720 // their gradient slots, so depositing them here bought nothing but a halo deposit, a redistribution and
721 // a coarsen-and-fill-ghosts apiece -- and a chemistry typically corrects on one or two species out of
722 // the set. The mesh densities of the skipped species are stale until prePlot() re-deposits all of them,
723 // which is ahead of every reader that wants them current.
724 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
725 if ((this->m_physics)->needGradient(solverIt.index())) {
726 solverIt()->depositParticles();
727 }
728 }
729
730 this->computeDensityGradients();
731 m_timer.stopEvent("Gradient calculation");
732 }
733
734 // Resolve secondary emission. We have filled the relevant particles in the transport step -- this can be done
735 // either before or after the reactions.
736 if (m_emitSecondaryParticlesBeforeReactions) {
737 this->barrier();
738 m_timer.startEvent("EB particle injection");
739 if (this->needSecondaryEmissionEB()) {
740 this->fillSecondaryEmissionEB(a_dt);
741 this->resolveSecondaryEmissionEB(a_dt);
742 }
743 m_timer.stopEvent("EB particle injection");
744 }
745
746 // Sort the particles and photons per cell so we can call reaction algorithms
747 this->barrier();
748 m_timer.startEvent("Sort by cell");
749 (this->m_ito)->organizeParticlesByCell(ItoSolver::WhichContainer::Bulk);
750 this->sortPhotonsByCell(McPhoto::WhichContainer::Bulk);
751 this->sortPhotonsByCell(McPhoto::WhichContainer::Source);
752 m_timer.stopEvent("Sort by cell");
753
754 // Run the Kinetic Monte Carlo reaction kernels. These are evaluated at (1-theta)*E^k + theta*E^(k+1) rather
755 // than at the end-of-step field: in the semi-implicit formulation E^(k+1) is the already-screened field, so
756 // evaluating the whole reactive substep there biases the rate coefficients low wherever the plasma screens
757 // the field. m_reactiveElectricField holds E^k, and m_electricFieldFluid holds E^(k+1) at this point.
758 //
759 // NOTE: This centering applies to the reactions only. The drift update must keep using E^(k+1) with the
760 // lagged mobility mu^k because that is the pairing the semi-implicit Poisson operator was built for.
761 this->barrier();
762 m_timer.startEvent("Reaction network");
763 if (m_reactiveFieldCentering >= 1.0) {
764 // Pure E^(k+1). m_reactiveElectricField was never filled -- see "Store E^k" above.
765 this->advanceReactionNetwork(this->m_electricFieldFluid, a_dt);
766 }
767 else {
768 // Pure E^k needs no blending; m_reactiveElectricField already holds it.
769 if (m_reactiveFieldCentering > 0.0) {
770 DataOps::scale(m_reactiveElectricField, 1.0 - m_reactiveFieldCentering);
771 DataOps::incr(m_reactiveElectricField, this->m_electricFieldFluid, m_reactiveFieldCentering);
772 }
773
774 this->advanceReactionNetwork(m_reactiveElectricField, a_dt);
775 }
776 m_timer.stopEvent("Reaction network");
777
778 // Merge super-particles down to the target after the chemistry advance created/removed particles.
779 // Routed through the public ItoSolver::makeSuperparticles() so both per-cell and AMR-wide
780 // (nn_amr, any nn_search backend) merge algorithms run, chosen by ItoSolver.merge_method.
781 // makeSuperparticles() cell-sorts internally as needed and returns the container patch-organized.
782 this->barrier();
783 m_timer.startEvent("Make superparticles");
784 if (this->m_mergeInterval > 0 && (this->m_timeStep + 1) % this->m_mergeInterval == 0) {
785 (this->m_ito)->makeSuperparticles(ItoSolver::WhichContainer::Bulk);
786 }
787 m_timer.stopEvent("Make superparticles");
788
789 // Sort particles per patch.
790 this->barrier();
791 m_timer.startEvent("Sort by patch");
792 (this->m_ito)->organizeParticlesByPatch(ItoSolver::WhichContainer::Bulk);
793 this->sortPhotonsByPatch(McPhoto::WhichContainer::Bulk);
794 this->sortPhotonsByPatch(McPhoto::WhichContainer::Source);
795 m_timer.stopEvent("Sort by patch");
796
797 // Resolve secondary emission. We have filled the relevant particles in the transport step -- this can be done
798 // either before or after the reactions.
799 if (!m_emitSecondaryParticlesBeforeReactions) {
800 this->barrier();
801 m_timer.startEvent("EB particle injection");
802 if (this->needSecondaryEmissionEB()) {
803 this->fillSecondaryEmissionEB(a_dt);
804 this->resolveSecondaryEmissionEB(a_dt);
805 }
806 m_timer.stopEvent("EB particle injection");
807 }
808
809 // Remove particles that are inside the EB -- this is not a part of the algorithm, just a safety measure to make sure
810 // we don't do things with particles that lie inside the EB.
811 this->barrier();
812 m_timer.startEvent("Remove covered");
813 this->removeCoveredParticles(SpeciesSubset::AllMobileOrDiffusive, EBRepresentation::Discrete, this->m_toleranceEB);
814 m_timer.stopEvent("Remove covered");
815
816 // Clear BC data holders.
817 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
818 solverIt()->clear(ItoSolver::WhichContainer::EB);
819 solverIt()->clear(ItoSolver::WhichContainer::Domain);
820 }
821
822 // Prepare for the next time step. This is computeDriftVelocities() minus setItoVelocityFunctions():
823 // that call rebuilds the mesh field sgn(Z)*E from m_electricFieldParticle, and no Poisson solve has run
824 // since "Step-compute v" built exactly the same field from exactly the same E. interpolateVelocities()
825 // only reads it, so it is still valid; the particles moved, not the field.
826 //
827 // setCdrVelocityFunctions() is NOT redundant in the same way and is kept:
828 // multiplyCdrVelocitiesByMobilities() scales the CDR velocity in place, so the field has to be reset to
829 // sgn(Z)*E before each scaling or the mobility compounds across steps.
830 this->barrier();
831 m_timer.startEvent("Post-compute v");
832 this->setCdrVelocityFunctions();
833 this->computeMobilities();
834 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
835 solverIt()->interpolateVelocities();
836 }
837 this->multiplyCdrVelocitiesByMobilities();
838 m_timer.stopEvent("Post-compute v");
839
840 this->barrier();
841 m_timer.startEvent("Post-compute D");
842 this->computeDiffusionCoefficients();
843 m_timer.stopEvent("Post-compute D");
844
845 this->computePhysicsDt();
846
847 if ((this->m_profile)) {
848 m_timer.eventReport(pout(), false);
849 }
850
851 m_timer.clear();
852
853 // Compute the maximum field (in Townsend).
854 this->m_maxReducedField = this->computeMaxReducedElectricField(this->m_plasmaPhase);
855
856 return a_dt;
857}
858
859template <typename I, typename C, typename R, typename F>
860void
861ItoKMCGodunovStepper<I, C, R, F>::preRegrid(const int a_lmin, const int a_oldFinestLevel) noexcept
862{
863 CH_TIME("ItoKMCGodunovStepper::preRegrid");
864 if (this->m_verbosity > 5) {
865 pout() << "ItoKMCGodunovStepper::preRegrid" << endl;
866 }
867
868 const int numItoSpecies = (this->m_physics)->getNumItoSpecies();
869 const int numCdrSpecies = (this->m_physics)->getNumCdrSpecies();
870 const int numPlasmaSpecies = (this->m_physics)->getNumPlasmaSpecies();
871 const int numPhotonSpecies = (this->m_physics)->getNumPhotonSpecies();
872
873 ItoKMCStepper<I, C, R, F>::preRegrid(a_lmin, a_oldFinestLevel);
874
875 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
876 const int idx = solverIt.index();
877
878 m_conductivityParticles[idx]->preRegrid();
879 m_irregularParticles[idx]->preRegrid();
880 m_rhoDaggerParticles[idx]->preRegrid();
881 }
882
883 this->m_amr->allocate(m_scratchSemiImplicitRhoCDR, this->m_fluidRealm, this->m_plasmaPhase, 1);
884 this->m_amr->allocate(m_scratchSemiImplicitConductivityCDR, this->m_fluidRealm, this->m_plasmaPhase, 1);
885
886 DataOps::copy(m_scratchSemiImplicitRhoCDR, m_semiImplicitRhoCDR);
887 DataOps::copy(m_scratchSemiImplicitConductivityCDR, m_semiImplicitConductivityCDR);
888
889 // Release unnecessary storage.
890 for (int i = 0; i < numCdrSpecies; i++) {
891 m_cdrDivD[i].clear();
892 }
893
894 m_semiImplicitRhoCDR.clear();
895 m_semiImplicitConductivityCDR.clear();
896}
897
898template <typename I, typename C, typename R, typename F>
899void
901 const int a_oldFinestLevel,
902 const int a_newFinestLevel) noexcept
903{
904 CH_TIME("ItoKMCGodunovStepper::regrid");
905 if (this->m_verbosity > 5) {
906 pout() << "ItoKMCGodunovStepper::regrid" << endl;
907 }
908
909 m_timer = Timer("ItoKMCGodunovStepper::regrid");
910
911 // A special flag for aborting the simulation if the user did NOT put checkpoint particles in the checkpoint
912 // file but still try to regrid on restart.
913 if (!m_canRegridOnRestart) {
914 const std::string baseErr = "ItoKMCGodunovStepper::regrid -- can't regrid because";
915 const std::string err1 = "checkpoint file does not contain particles. Set Driver.initial_regrids=0";
916
917 pout() << baseErr + err1 << endl;
918
919 MayDay::Error((baseErr + err1).c_str());
920 }
921
922 // Regrid solvers
923 m_timer.startEvent("Regrid ItoSolver");
924 (this->m_ito)->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
925 if (this->m_timeStep == 0) {
926 // Necessary because first time step is not semi-implicit, so it will use the densities from the
927 // Ito and CDr solvers. However, ItoSolver does not re-deposit the particles during regrid, so we
928 // enforce it here.
929 (this->m_ito)->depositParticles();
930 }
931 m_timer.stopEvent("Regrid ItoSolver");
932
933 m_timer.startEvent("Regrid CdrSolver");
934 (this->m_cdr)->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
935 m_timer.stopEvent("Regrid CdrSolver");
936
937 m_timer.startEvent("Regrid FieldSolver");
938 (this->m_fieldSolver)->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
939 m_timer.stopEvent("Regrid FieldSolver");
940
941 m_timer.startEvent("Regrid RTE");
942 (this->m_rte)->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
943 m_timer.stopEvent("Regrid RTE");
944
945 m_timer.startEvent("Regrid SurfaceODESolver");
946 this->m_sigmaSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
947 m_timer.stopEvent("Regrid SurfaceODESolver");
948
949 // Allocate internal memory for ItoKMCGodunovStepper now....
950 m_timer.startEvent("Allocate internals");
951 this->allocateInternals();
952 m_timer.stopEvent("Allocate internals");
953
954 // We need to remap/regrid the stored data required for the semi-implicit update as well.
955 m_timer.startEvent("Remap algorithm-particles");
956 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
957 const int idx = solverIt.index();
958 (this->m_amr)->remapToNewGrids(*m_rhoDaggerParticles[idx], a_lmin, a_newFinestLevel);
959 (this->m_amr)->remapToNewGrids(*m_conductivityParticles[idx], a_lmin, a_newFinestLevel);
960 (this->m_amr)->remapToNewGrids(*m_irregularParticles[idx], a_lmin, a_newFinestLevel);
961 }
962 m_timer.stopEvent("Remap algorithm-particles");
963
964 // Also regrid the space charge density contribution from the CDR equations
965 this->m_amr->interpToNewGrids(m_semiImplicitRhoCDR,
966 m_scratchSemiImplicitRhoCDR,
967 this->m_plasmaPhase,
968 a_lmin,
969 a_oldFinestLevel,
970 a_newFinestLevel,
971 EBCoarseToFineInterp::Type::ConservativeMinMod);
972
973 this->m_amr->interpToNewGrids(m_semiImplicitConductivityCDR,
974 m_scratchSemiImplicitConductivityCDR,
975 this->m_plasmaPhase,
976 a_lmin,
977 a_oldFinestLevel,
978 a_newFinestLevel,
979 EBCoarseToFineInterp::Type::ConservativeMinMod);
980
981 // Set up the field solver with standard coefficients or with
982 // modified coefficients if we are reusing data from the last time step.
983 m_timer.startEvent("Setup field solver");
984 (this->m_fieldSolver)->setupSolver();
985 this->computeConductivities(m_conductivityParticles, true);
986 this->setupSemiImplicitPoisson(this->m_prevDt);
987 m_timer.stopEvent("Setup field solver");
988
989 // Solve the Poisson equation.
990 m_timer.startEvent("Solve Poisson");
991 if (this->m_timeStep == 0) {
992 this->computeSpaceChargeDensity();
993 }
994 else {
995 this->depositPointParticles(m_rhoDaggerParticles, SpeciesSubset::All);
996 this->computeSemiImplicitRho();
997 }
998
999 const bool converged = this->solvePoisson();
1000
1001 if (!converged) {
1002 const std::string errMsg = "ItoKMCGodunovStepper::regrid - Poisson solve did not converge after regrid";
1003
1004 pout() << errMsg << endl;
1005
1006 if (this->m_abortOnFailure) {
1007 MayDay::Error(errMsg.c_str());
1008 }
1009 }
1010 m_timer.stopEvent("Solve Poisson");
1011
1012 // The regrid super-particle merge now runs inside ItoSolver::regrid() -- above, in the "Regrid Ito
1013 // solvers" event -- so that the de-refinement pile-up is merged while it is still held as the reduced
1014 // 53 B particle rather than the full 149 B ItoParticle. Two consequences worth knowing:
1015 //
1016 // - This timer loses its "Make superparticles" line for the regrid. The advance() one stays, since
1017 // merge_interval stays. Profile ItoSolver directly if the regrid merge needs attributing again;
1018 // it keeps its own CH_TIME.
1019 // - The merge now happens BEFORE the semi-implicit Poisson solve above rather than after it, which
1020 // is what the base ItoKMCStepper::regrid() already did. Only observable at m_timeStep == 0, where
1021 // computeSpaceChargeDensity() deposits the bulk particles; every later step solves from
1022 // m_rhoDaggerParticles, and the conductivity comes from m_conductivityParticles. A merge
1023 // conserves total weight, so the deposited density is preserved to the merge's spatial accuracy.
1024
1025 // Now let Ihe ito solver deposit its actual particles... In the above it deposit m_rhoDaggerParticles.
1026 m_timer.startEvent("Deposit particles");
1027 (this->m_ito)->depositParticles();
1028 m_timer.stopEvent("Deposit particles");
1029
1030 // Recompute new velocities and diffusion coefficients
1031 m_timer.startEvent("Prepare next step");
1032 this->computeDiffusionCoefficients();
1033 this->computeDriftVelocities();
1034 m_timer.stopEvent("Prepare next step");
1035
1036 m_timer.eventReport(pout(), false);
1037
1038 // No reason to have these lying around.
1039 m_scratchSemiImplicitRhoCDR.clear();
1040 m_scratchSemiImplicitConductivityCDR.clear();
1041
1042 // Fill the neutral density on the mesh
1043 this->fillNeutralDensity();
1044}
1045
1046template <typename I, typename C, typename R, typename F>
1047void
1049{
1050 CH_TIME("ItoKMCGodunovStepper::setOldPositions");
1051 if (this->m_verbosity > 5) {
1052 pout() << this->m_name + "::setOldPositions" << endl;
1053 }
1054
1055 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1056 RefCountedPtr<ItoSolver>& solver = solverIt();
1057
1058 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
1059 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
1060 const DataIterator& dit = dbl.dataIterator();
1061
1062 auto& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk)[lvl];
1063
1064 const int nbox = dit.size();
1065
1066#pragma omp parallel for schedule(runtime)
1067 for (int mybox = 0; mybox < nbox; mybox++) {
1068 const DataIndex& din = dit[mybox];
1069
1070 ParticleSoA<ItoParticle>& leaf = particles[din];
1071
1072 double* const pos[SpaceDim] = {D_DECL(leaf.positionColumn(0), leaf.positionColumn(1), leaf.positionColumn(2))};
1073 double* const oldPos[SpaceDim] = {D_DECL(leaf.template column<&ItoParticle::old_x>(),
1074 leaf.template column<&ItoParticle::old_y>(),
1075 leaf.template column<&ItoParticle::old_z>())};
1076
1077 ParticleLoops::loop(leaf, [&](const std::size_t i) {
1078 D_DECL(oldPos[0][i] = pos[0][i], oldPos[1][i] = pos[1][i], oldPos[2][i] = pos[2][i]);
1079 });
1080 }
1081 }
1082 }
1083}
1084
1085template <typename I, typename C, typename R, typename F>
1086void
1088 const SpeciesSubset a_subset) noexcept
1089{
1090 CH_TIME("ItoKMCGodunovStepper::remapPointParticles");
1091 if (this->m_verbosity > 5) {
1092 pout() << this->m_name + "::remapPointParticles" << endl;
1093 }
1094
1095 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1096 RefCountedPtr<ItoSolver>& solver = solverIt();
1097 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1098
1099 const int idx = solverIt.index();
1100
1101 const bool mobile = solver->isMobile();
1102 const bool diffusive = solver->isDiffusive();
1103 const bool charged = species->getChargeNumber() != 0;
1104
1105 switch (a_subset) {
1106 case SpeciesSubset::All: {
1107 a_particles[idx]->remap();
1108
1109 break;
1110 }
1111 case SpeciesSubset::AllMobile: {
1112 if (mobile) {
1113 a_particles[idx]->remap();
1114 }
1115
1116 break;
1117 }
1118 case SpeciesSubset::AllDiffusive: {
1119 if (diffusive) {
1120 a_particles[idx]->remap();
1121 }
1122
1123 break;
1124 }
1125 case SpeciesSubset::AllMobileOrDiffusive: {
1126 if (mobile || diffusive) {
1127 a_particles[idx]->remap();
1128 }
1129
1130 break;
1131 }
1132 case SpeciesSubset::AllMobileAndDiffusive: {
1133 if (mobile && diffusive) {
1134 a_particles[idx]->remap();
1135 }
1136
1137 break;
1138 }
1139 case SpeciesSubset::Charged: {
1140 if (charged) {
1141 a_particles[idx]->remap();
1142 }
1143
1144 break;
1145 }
1146 case SpeciesSubset::ChargedMobile: {
1147 if (charged && mobile) {
1148 a_particles[idx]->remap();
1149 }
1150
1151 break;
1152 }
1153 case SpeciesSubset::ChargedDiffusive: {
1154 if (charged && diffusive) {
1155 a_particles[idx]->remap();
1156 }
1157
1158 break;
1159 }
1160 case SpeciesSubset::ChargedMobileOrDiffusive: {
1161 if (charged && (mobile || diffusive)) {
1162 a_particles[idx]->remap();
1163 }
1164
1165 break;
1166 }
1167 case SpeciesSubset::ChargedMobileAndDiffusive: {
1168 if (charged && (mobile && diffusive)) {
1169 a_particles[idx]->remap();
1170 }
1171
1172 break;
1173 }
1174 case SpeciesSubset::Stationary: {
1175 if (!mobile && !diffusive) {
1176 a_particles[idx]->remap();
1177 }
1178
1179 break;
1180 }
1181 default: {
1182 MayDay::Abort("ItoKMCGodunovStepper::remapPointParticles - logic bust");
1183
1184 break;
1185 }
1186 }
1187 }
1188}
1189
1190template <typename I, typename C, typename R, typename F>
1191void
1193 const Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_particles,
1194 const SpeciesSubset a_subset) noexcept
1195{
1196 CH_TIME("ItoKMCGodunovStepper::depositPointParticles");
1197 if (this->m_verbosity > 5) {
1198 pout() << this->m_name + "::depositPointParticles" << endl;
1199 }
1200
1201 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1202 RefCountedPtr<ItoSolver>& solver = solverIt();
1203 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1204
1205 const int idx = solverIt.index();
1206
1207 const bool mobile = solver->isMobile();
1208 const bool diffusive = solver->isDiffusive();
1209 const bool charged = species->getChargeNumber() != 0;
1210
1211 switch (a_subset) {
1212 case SpeciesSubset::All: {
1213 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1214
1215 break;
1216 }
1217 case SpeciesSubset::AllMobile: {
1218 if (mobile) {
1219 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1220 }
1221
1222 break;
1223 }
1224 case SpeciesSubset::AllDiffusive: {
1225 if (diffusive) {
1226 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1227 }
1228
1229 break;
1230 }
1231 case SpeciesSubset::AllMobileOrDiffusive: {
1232 if (mobile || diffusive) {
1233 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1234 }
1235 break;
1236 }
1237 case SpeciesSubset::AllMobileAndDiffusive: {
1238 if (mobile && diffusive) {
1239 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1240 }
1241 break;
1242 }
1243 case SpeciesSubset::Charged: {
1244 if (charged) {
1245 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1246 }
1247 break;
1248 }
1249 case SpeciesSubset::ChargedMobile: {
1250 if (charged && mobile) {
1251 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1252 }
1253 break;
1254 }
1255 case SpeciesSubset::ChargedDiffusive: {
1256 if (charged && diffusive) {
1257 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1258 }
1259
1260 break;
1261 }
1262 case SpeciesSubset::ChargedMobileOrDiffusive: {
1263 if (charged && (mobile || diffusive)) {
1264 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1265 }
1266
1267 break;
1268 }
1269 case SpeciesSubset::ChargedMobileAndDiffusive: {
1270 if (charged && (mobile && diffusive)) {
1271 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1272 }
1273
1274 break;
1275 }
1276 case SpeciesSubset::Stationary: {
1277 if (!mobile && !diffusive) {
1278 depositPointParticlesLikeSolver(solver, solver->getPhi(), *a_particles[idx]);
1279 }
1280
1281 break;
1282 }
1283 default: {
1284 MayDay::Abort("ItoKMCGodunovStepper::depositPointParticles - logic bust");
1285
1286 break;
1287 }
1288 }
1289 }
1290}
1291
1292template <typename I, typename C, typename R, typename F>
1293void
1295 const Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_particles,
1296 const SpeciesSubset a_subset) noexcept
1297{
1298 CH_TIME("ItoKMCGodunovStepper::clearPointParticles");
1299 if (this->m_verbosity > 5) {
1300 pout() << this->m_name + "::clearPointParticles" << endl;
1301 }
1302
1303 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1304 RefCountedPtr<ItoSolver>& solver = solverIt();
1305 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1306
1307 const int idx = solverIt.index();
1308
1309 const bool mobile = solver->isMobile();
1310 const bool diffusive = solver->isDiffusive();
1311 const bool charged = species->getChargeNumber() != 0;
1312
1313 switch (a_subset) {
1314 case SpeciesSubset::All: {
1315 a_particles[idx]->clearParticles();
1316
1317 break;
1318 }
1319 case SpeciesSubset::AllMobile: {
1320 if (mobile) {
1321 a_particles[idx]->clearParticles();
1322 }
1323
1324 break;
1325 }
1326 case SpeciesSubset::AllDiffusive: {
1327 if (diffusive) {
1328 a_particles[idx]->clearParticles();
1329 }
1330
1331 break;
1332 }
1333 case SpeciesSubset::AllMobileOrDiffusive: {
1334 if (mobile || diffusive) {
1335 a_particles[idx]->clearParticles();
1336 }
1337
1338 break;
1339 }
1340 case SpeciesSubset::AllMobileAndDiffusive: {
1341 if (mobile && diffusive) {
1342 a_particles[idx]->clearParticles();
1343 }
1344
1345 break;
1346 }
1347 case SpeciesSubset::Charged: {
1348 if (charged) {
1349 a_particles[idx]->clearParticles();
1350 }
1351
1352 break;
1353 }
1354 case SpeciesSubset::ChargedMobile: {
1355 if (charged && mobile) {
1356 a_particles[idx]->clearParticles();
1357 }
1358
1359 break;
1360 }
1361 case SpeciesSubset::ChargedDiffusive: {
1362 if (charged && diffusive) {
1363 a_particles[idx]->clearParticles();
1364 }
1365
1366 break;
1367 }
1368 case SpeciesSubset::ChargedMobileOrDiffusive: {
1369 if (charged && (mobile || diffusive)) {
1370 a_particles[idx]->clearParticles();
1371 }
1372
1373 break;
1374 }
1375 case SpeciesSubset::ChargedMobileAndDiffusive: {
1376 if (charged && (mobile && diffusive)) {
1377 a_particles[idx]->clearParticles();
1378 }
1379
1380 break;
1381 }
1382 case SpeciesSubset::Stationary: {
1383 if (!mobile && !diffusive) {
1384 a_particles[idx]->clearParticles();
1385 }
1386
1387 break;
1388 }
1389 default: {
1390 MayDay::Abort("ItoKMCGodunovStepper::clearPointParticles - logic bust");
1391
1392 break;
1393 }
1394 }
1395 }
1396}
1397
1398template <typename I, typename C, typename R, typename F>
1399void
1401{
1402 CH_TIME("ItoKMCGodunovStepper::computeCdrConductivity");
1403 if (this->m_verbosity > 5) {
1404 pout() << this->m_name + "::computeCdrConductivity" << endl;
1405 }
1406
1407 DataOps::setValue(m_semiImplicitConductivityCDR, 0.0);
1408
1409 for (auto solverIt = (this->m_cdr)->iterator(); solverIt.ok(); ++solverIt) {
1410 const RefCountedPtr<CdrSolver>& solver = solverIt();
1411 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
1412
1413 const int index = solverIt.index();
1414 const int Z = species->getChargeNumber();
1415
1416 if (Z != 0 && solver->isMobile()) {
1417 const EBAMRCellData& phi = solver->getPhi();
1418 const EBAMRCellData& mu = this->m_cdrMobilities[index];
1419
1420 DataOps::copy(this->m_fluidScratch1, phi);
1421 DataOps::multiply(this->m_fluidScratch1, mu);
1422
1423 DataOps::incr(m_semiImplicitConductivityCDR, this->m_fluidScratch1, 1.0 * std::abs(Z));
1424 }
1425 }
1426}
1427
1428template <typename I, typename C, typename R, typename F>
1429void
1431 const Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_particles,
1432 const bool a_useStoredCdrConductivity) noexcept
1433{
1434 CH_TIME("ItoKMCGodunovStepper::computeConductivities");
1435 if (this->m_verbosity > 5) {
1436 pout() << this->m_name + "::computeConductivities" << endl;
1437 }
1438
1439 this->computeCellConductivity((this->m_conductivityCell), a_particles, a_useStoredCdrConductivity);
1440 this->computeFaceConductivity();
1441}
1442
1443template <typename I, typename C, typename R, typename F>
1444void
1446 EBAMRCellData& a_conductivityCell,
1447 const Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_particles,
1448 const bool a_useStoredCdrConductivity) noexcept
1449{
1450 CH_TIME("ItoKMCGodunovStepper::computeCellConductivity(EBAMRCellData, ParticleContainer)");
1451 if (this->m_verbosity > 5) {
1452 pout() << this->m_name + "::computeCellConductivity(EBAMRCellData, ParticleContainer)" << endl;
1453 }
1454
1455 DataOps::setValue(a_conductivityCell, 0.0);
1456
1457 // Contribution from Ito solvers. The |Z|-weighted sum is formed on the particle realm and crossed to the
1458 // fluid realm once: the deposit has to happen per species (each has its own container, and the deposit
1459 // resets its target rather than incrementing it), but the cross-realm copy does not, and under dual-grid
1460 // it is a Copier exchange over the whole hierarchy.
1461 DataOps::setValue(m_particleScratchSum, 0.0);
1462
1463 bool anyItoConductivity = false;
1464
1465 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1466 RefCountedPtr<ItoSolver>& solver = solverIt();
1467 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1468
1469 const int idx = solverIt.index();
1470 const int Z = species->getChargeNumber();
1471
1472 if (Z != 0 && solver->isMobile()) {
1473 // Deposit on the particle realm. No zeroing first -- the deposition resets a_phi itself.
1474 depositPointParticlesLikeSolver(solver, this->m_particleScratch1, *a_particles[idx]);
1475
1476 DataOps::incr(m_particleScratchSum, this->m_particleScratch1, 1.0 * std::abs(Z));
1477
1478 anyItoConductivity = true;
1479 }
1480 }
1481
1482 if (anyItoConductivity) {
1483 // Zeroed because the copy below fills the valid region only, and DataOps::incr adds the whole FAB. The
1484 // ghost cells that neither the copy nor the later coarsen/interpolate/exchange reaches -- the ones
1485 // outside the physical domain -- would otherwise carry whatever the last user of the scratch left there.
1486 DataOps::setValue(this->m_fluidScratch1, 0.0);
1487
1488 (this->m_amr)->copyData(this->m_fluidScratch1, m_particleScratchSum);
1489 DataOps::incr(a_conductivityCell, this->m_fluidScratch1, 1.0);
1490 }
1491
1492 // Contribution from the CDR solvers, always taken from m_semiImplicitConductivityCDR. The flag decides only
1493 // whether that buffer is refreshed from the solver states first, which it must not be inside regrid(), where
1494 // the mobilities it is built from are not valid for the current grids.
1495 if (!a_useStoredCdrConductivity) {
1496 this->computeCdrConductivity();
1497 }
1498
1499 DataOps::incr(a_conductivityCell, m_semiImplicitConductivityCDR, 1.0);
1500
1501 // Conductivity is mobility * weight * Q
1502 DataOps::scale(a_conductivityCell, Units::Qe);
1503
1504 // Coarsen, update ghost cells and interpolate to centroids. User can ask for bi/tri-linear filtering.
1505 (this->m_amr)->arithmeticAverage(a_conductivityCell, this->m_fluidRealm, (this->m_plasmaPhase));
1506 (this->m_amr)->interpGhostPwl(a_conductivityCell, this->m_fluidRealm, (this->m_plasmaPhase));
1507
1508 // User can choose to filter the conductivity. Need to sync after each smoothing.
1509 if (m_condFilterNum > 0 && m_condFilterMaxStride > 0) {
1510 for (int i = 0; i < m_condFilterNum; i++) {
1511 for (int curStride = 1; curStride <= m_condFilterMaxStride; curStride++) {
1512 DataOps::filterSmooth(a_conductivityCell, m_condFilterAlpha, curStride, true);
1513
1514 (this->m_amr)->arithmeticAverage(a_conductivityCell, this->m_fluidRealm, (this->m_plasmaPhase));
1515 (this->m_amr)->interpGhostPwl(a_conductivityCell, this->m_fluidRealm, (this->m_plasmaPhase));
1516 }
1517 }
1518 }
1519
1520 (this->m_amr)->interpToCentroids(a_conductivityCell, this->m_fluidRealm, (this->m_plasmaPhase));
1521
1522 // The sum itself is non-negative, but the CDR part is interpolated across regrids, filterSmooth may smooth
1523 // it, and interpToCentroids uses stencils with negative weights. Floor after the last of those so that the
1524 // bound holds where the data is consumed -- a negative conductivity enters the field solve as
1525 // eps + sigma*dt/eps0 < eps.
1526 DataOps::floor(a_conductivityCell, 0.0, (this->m_amr)->getVofIterator(this->m_fluidRealm, this->m_plasmaPhase));
1527}
1528
1529template <typename I, typename C, typename R, typename F>
1530void
1532{
1533 CH_TIME("ItoKMCGodunovStepper::computeFaceConductivity");
1534 if (this->m_verbosity > 5) {
1535 pout() << this->m_name + "::computeFaceConductivity" << endl;
1536 }
1537
1538 DataOps::setValue((this->m_conductivityFace), 0.0);
1539 DataOps::setValue((this->m_conductivityEB), 0.0);
1540
1541 // Average the cell-centered conductivity to faces. Note that this includes one "ghost face", which we need
1542 // because the multigrid solver will interpolate face-centered conductivities to face centroids.
1543 const Average average = Average::Arithmetic;
1544 const int tanGhost = 1;
1545 const Interval interv(0, 0);
1546
1548 (this->m_conductivityFace),
1549 (this->m_conductivityCell),
1550 (this->m_amr)->getDomains(),
1551 tanGhost,
1552 interv,
1553 interv,
1554 average,
1555 (this->m_amr)->getFaceIteratorWithTangentialGhosts(this->m_fluidRealm, this->m_plasmaPhase));
1556
1557 // Set the EB conductivity.
1558 DataOps::incr((this->m_conductivityEB),
1559 (this->m_conductivityCell),
1560 1.0,
1561 (this->m_amr)->getVofIterator(this->m_fluidRealm, this->m_plasmaPhase));
1562}
1563
1564template <typename I, typename C, typename R, typename F>
1565void
1567{
1568 CH_TIMERS("ItoKMCGodunovStepper::computeSemiImplicitRho");
1569 CH_TIMER("ItoKMCGodunovStepper::computeSemiImplicitRho::plasma_phase", t1);
1570 CH_TIMER("ItoKMCGodunovStepper::computeSemiImplicitRho::solid_phase", t2);
1571 CH_TIMER("ItoKMCGodunovStepper::computeSemiImplicitRho::filter", t3);
1572 if (this->m_verbosity > 5) {
1573 pout() << this->m_name + "::computeSemiImplicitRho" << endl;
1574 }
1575
1576 // Soft requirement?
1577 CH_assert(this->m_plasmaPhase == phase::gas);
1578
1579 const RefCountedPtr<MultiFluidIndexSpace>& mfis = (this->m_computationalGeometry)->getMfIndexSpace();
1580 const Vector<Dielectric>& dielectrics = (this->m_computationalGeometry)->getDielectrics();
1581
1582 const bool hasDielectrics = (mfis->numPhases() > 1) && (dielectrics.size() > 0);
1583
1584 MFAMRCellData& rho = this->m_fieldSolver->getRho();
1585 EBAMRCellData rhoGas = (this->m_amr)->alias(phase::gas, rho);
1586 EBAMRCellData rhoSolid;
1587
1588 if (hasDielectrics) {
1589 rhoSolid = (this->m_amr)->alias(phase::solid, rho);
1590 }
1591
1592 DataOps::setValue(rho, 0.0);
1593
1594 // Contribution from Ito solvers to the gas-side space charge density. The Z-weighted sum is formed on the
1595 // particle realm -- where every solver's density already lives -- and crossed to the fluid realm once
1596 // instead of once per species. See computeCellConductivity(), which does the same for the conductivity.
1597 CH_START(t1);
1598 DataOps::setValue(m_particleScratchSum, 0.0);
1599
1600 bool anyItoCharge = false;
1601
1602 for (auto solverIt = this->m_ito->iterator(); solverIt.ok(); ++solverIt) {
1603 const RefCountedPtr<ItoSolver>& solver = solverIt();
1604 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1605 const int Z = species->getChargeNumber();
1606
1607 if (Z != 0) {
1608 // The per-species weight stays Z*Qe rather than being factored out of the loop: the terms are then
1609 // summed in the same order and with the same values as when each was incremented into rhoGas
1610 // separately, so the result is bit-for-bit what it was before the accumulation moved realms.
1611 DataOps::incr(m_particleScratchSum, solver->getPhi(), 1.0 * Z * Units::Qe);
1612
1613 anyItoCharge = true;
1614 }
1615 }
1616
1617 if (anyItoCharge) {
1618 // See computeCellConductivity() for why the landing buffer is zeroed first.
1619 DataOps::setValue(this->m_fluidScratch1, 0.0);
1620
1621 (this->m_amr)->copyData(this->m_fluidScratch1, m_particleScratchSum);
1622 DataOps::incr(rhoGas, this->m_fluidScratch1, 1.0);
1623 }
1624
1625 // Contribution from CDR equations to the gas-side space charge density.
1626 DataOps::incr(rhoGas, m_semiImplicitRhoCDR, 1.0);
1627 CH_STOP(t1);
1628
1629 // Contribution from gas-side particles that diffused into EBs. This might be necessary near dielectric EBs as a
1630 // comparatively small correction in the space charge density. There should be no contribution from the CDR solvers
1631 // because the diffusion-only divergence term was computed using homogeneous Neumann boundary conditions.
1632 if (hasDielectrics) {
1633 CH_START(t2);
1634
1635 // The buffers are members allocated once (see allocateInternals) rather than a pair of hierarchies built
1636 // and thrown away on every time step.
1637 DataOps::setValue(m_particleScratchSumSolid, 0.0);
1638
1639 bool anySolidCharge = false;
1640
1641 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1642 const RefCountedPtr<ItoSolver>& solver = solverIt();
1643 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1644 const int Z = species->getChargeNumber();
1645
1646 if (Z != 0) {
1647 // The cut-cell strategy is a literal here, and must stay one: this deposits onto phase::solid, whose
1648 // ebisbox.normal() points into the SOLID. Routing a solver's own setting here would mirror across the
1649 // same embedded boundary with the fluid side swapped, pushing particles that were deliberately placed
1650 // inside the dielectric back out into the gas. NGP reproduces the historical behaviour exactly.
1651 //
1652 // No zeroing first -- the deposition resets its target itself.
1653 (this->m_amr)
1654 ->depositWeight(m_particleScratchSolid,
1655 this->m_particleRealm,
1657 *m_rhoDaggerParticles[solverIt.index()],
1658 DepositionType::CIC,
1659 CoarseFineDeposition::Halo,
1661
1662 // Weighted per species, not once at the end -- see the gas-phase loop above.
1663 DataOps::incr(m_particleScratchSumSolid, m_particleScratchSolid, 1.0 * Z * Units::Qe);
1664
1665 anySolidCharge = true;
1666 }
1667 }
1668
1669 if (anySolidCharge) {
1670 // See computeCellConductivity() for why the landing buffer is zeroed first.
1671 DataOps::setValue(m_fluidScratchSolid, 0.0);
1672
1673 (this->m_amr)->copyData(m_fluidScratchSolid, m_particleScratchSumSolid);
1674 DataOps::incr(rhoSolid, m_fluidScratchSolid, 1.0);
1675 }
1676
1677 CH_STOP(t2);
1678 }
1679
1680 // Sync across levels
1681 this->m_amr->arithmeticAverage(rho, this->m_fluidRealm);
1682 this->m_amr->interpGhostPwl(rho, this->m_fluidRealm);
1683
1684 // User can choose to filter the space charge density. Need to sync after each smoothing.
1685 if (m_rhoFilterNum > 0 && m_rhoFilterMaxStride > 0) {
1686 CH_START(t3);
1687 for (int i = 0; i < m_rhoFilterNum; i++) {
1688 for (int curStride = 1; curStride <= m_rhoFilterMaxStride; curStride++) {
1689
1690 DataOps::filterSmooth(rhoGas, m_rhoFilterAlpha, curStride, true);
1691
1692 this->m_amr->arithmeticAverage(rhoGas, this->m_fluidRealm, this->m_plasmaPhase);
1693 this->m_amr->interpGhost(rhoGas, this->m_fluidRealm, this->m_plasmaPhase);
1694 }
1695 }
1696 CH_STOP(t3);
1697 }
1698
1699 // Put data on centroids.
1700 this->m_amr->interpToCentroids(rhoGas, this->m_fluidRealm, phase::gas);
1701 if (hasDielectrics) {
1702 this->m_amr->interpToCentroids(rhoSolid, this->m_fluidRealm, phase::solid);
1703 }
1704}
1705
1706template <typename I, typename C, typename R, typename F>
1707void
1709{
1710 CH_TIME("ItoKMCGodunovStepper::setupSemiImplicitPoisson");
1711 if (this->m_verbosity > 5) {
1712 pout() << this->m_name + "::setupSemiImplicitPoisson" << endl;
1713 }
1714
1715 // Set coefficients as usual
1716 (this->m_fieldSolver)->setPermittivities();
1717
1718 // Get the permittivities
1719 MFAMRCellData& permCell = (this->m_fieldSolver)->getPermittivityCell();
1720 MFAMRFluxData& permFace = (this->m_fieldSolver)->getPermittivityFace();
1721 MFAMRIVData& permEB = (this->m_fieldSolver)->getPermittivityEB();
1722
1723 // Get handles to the gas-phase permittivities.
1724 EBAMRFluxData permFaceGas = (this->m_amr)->alias((this->m_plasmaPhase), permFace);
1725 EBAMRIVData permEBGas = (this->m_amr)->alias((this->m_plasmaPhase), permEB);
1726
1727 // Increment the field solver permittivities by a_factor*sigma. After this, the "permittivities" are
1728 // given by epsr + a_factor*sigma
1729 DataOps::incr(permFaceGas, (this->m_conductivityFace), a_dt / Units::eps0);
1730 DataOps::incr(permEBGas,
1731 (this->m_conductivityEB),
1732 a_dt / Units::eps0,
1733 (this->m_amr)->getVofIterator(this->m_fluidRealm, this->m_plasmaPhase));
1734
1735 // Coarsen coefficients.
1736 (this->m_amr)->arithmeticAverage(permFaceGas, this->m_fluidRealm, (this->m_plasmaPhase));
1737 (this->m_amr)->arithmeticAverage(permEBGas, this->m_fluidRealm, (this->m_plasmaPhase));
1738
1739 // Set up the solver with the "permittivities"
1740 (this->m_fieldSolver)->setSolverPermittivities(permCell, permFace, permEB);
1741}
1742
1743template <typename I, typename C, typename R, typename F>
1744void
1746 Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_particles,
1747 const EBRepresentation a_representation,
1748 const Real a_tolerance) const noexcept
1749{
1750 CH_TIME("ItoKMCGodunovStepper::removeCoveredPointParticles");
1751 if (this->m_verbosity > 5) {
1752 pout() << this->m_name + "::removeCoveredPointParticles" << endl;
1753 }
1754
1755 for (int i = 0; i < a_particles.size(); i++) {
1756 if (a_particles[i] != nullptr) {
1757 ParticleContainer<NoPayload>& particles = *a_particles[i];
1758
1759 switch (a_representation) {
1760 case EBRepresentation::Discrete: {
1761 (this->m_amr)->removeCoveredParticlesDiscrete(particles, (this->m_plasmaPhase), a_tolerance);
1762
1763 break;
1764 }
1765 case EBRepresentation::ImplicitFunction: {
1766 (this->m_amr)->removeCoveredParticlesIF(particles, (this->m_plasmaPhase), a_tolerance);
1767
1768 break;
1769 }
1770 case EBRepresentation::Voxel: {
1771 (this->m_amr)->removeCoveredParticlesVoxels(particles, (this->m_plasmaPhase));
1772
1773 break;
1774 }
1775 default: {
1776 MayDay::Error("ItoKMCGodunovStepper::removeCoveredParticles - logic bust");
1777 }
1778 }
1779 }
1780 }
1781}
1782
1783template <typename I, typename C, typename R, typename F>
1784void
1786 Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_conductivityParticles) noexcept
1787{
1788 CH_TIME("ItoKMCGodunovStepper::copyConductivityParticles");
1789 if (this->m_verbosity > 5) {
1790 pout() << this->m_name + "::copyConductivityParticles" << endl;
1791 }
1792
1793 // Clear particles first.
1794 this->clearPointParticles(a_conductivityParticles, SpeciesSubset::All);
1795
1796 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1797 const RefCountedPtr<ItoSolver>& solver = solverIt();
1798 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1799
1800 const int idx = solverIt.index();
1801 const int Z = species->getChargeNumber();
1802
1803 if (Z != 0 && solver->isMobile()) {
1804 const ParticleContainer<ItoParticle>& solverParticles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
1805
1806 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
1807 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
1808 const DataIterator& dit = dbl.dataIterator();
1809
1810 const int nbox = dit.size();
1811
1812#pragma omp parallel for schedule(runtime)
1813 for (int mybox = 0; mybox < nbox; mybox++) {
1814 const DataIndex& din = dit[mybox];
1815
1816 const ParticleSoA<ItoParticle>& leaf = solverParticles[lvl][din];
1817
1818 ParticleSoA<NoPayload>& pointParticles = (*a_conductivityParticles[idx])[lvl][din];
1819 ParticleSoA<NoPayload>& irregParticles = (*m_irregularParticles[idx])[lvl][din];
1820
1821 // One point particle per particle, plus whatever the catenate below brings. Reserved in one go
1822 // because reserve() sizes the capacity exactly: reserving only the loop's own count would leave
1823 // the container full, and the catenate would immediately reallocate it.
1824 pointParticles.reserve(pointParticles.size() + leaf.size() + irregParticles.size());
1825
1826 for (std::size_t i = 0; i < leaf.size(); i++) {
1827 const RealVect pos = leaf.position(i);
1828 const Real weight = leaf.weight(i);
1829 const Real mobility = leaf.template get<&ItoParticle::mobility>(i);
1830
1831 pointParticles.append(pos, weight * mobility);
1832 }
1833
1834 pointParticles.catenate(irregParticles);
1835 }
1836 }
1837 }
1838 }
1839}
1840
1841template <typename I, typename C, typename R, typename F>
1842bool
1844{
1845 CH_TIME("ItoKMCGodunovStepper::solvePoisson()");
1846 if (this->m_verbosity > 5) {
1847 pout() << this->m_name + "::solvePoisson()" << endl;
1848 }
1849
1850 // Solve the Poisson equation and compute the cell-centered electric field.
1851 MFAMRCellData& phi = this->m_fieldSolver->getPotential();
1852 MFAMRCellData& rho = this->m_fieldSolver->getRho();
1853 EBAMRIVData& sigma = this->m_sigmaSolver->getPhi();
1854
1855 const bool converged = (this->m_fieldSolver)->solve(phi, rho, sigma, false);
1856
1857 (this->m_fieldSolver)->computeElectricField();
1858
1859 // Copy the electric field to appropriate data holders and perform center-to-centroid
1860 // interpolation.
1861 EBAMRCellData E;
1862 (this->m_amr)->allocatePointer(E, this->m_fluidRealm);
1863 (this->m_amr)->alias(E, this->m_plasmaPhase, (this->m_fieldSolver)->getElectricField());
1864
1865 // Fluid realm
1866 (this->m_amr)->copyData(this->m_electricFieldFluid, E);
1867 (this->m_amr)->conservativeAverage(this->m_electricFieldFluid, this->m_fluidRealm, this->m_plasmaPhase);
1868 (this->m_amr)->interpGhostPwl(this->m_electricFieldFluid, this->m_fluidRealm, this->m_plasmaPhase);
1869 (this->m_amr)->interpToCentroids(this->m_electricFieldFluid, this->m_fluidRealm, this->m_plasmaPhase);
1870
1871 // Particle realm
1872 (this->m_amr)->copyData(this->m_electricFieldParticle, E);
1873 (this->m_amr)->conservativeAverage(this->m_electricFieldParticle, this->m_particleRealm, this->m_plasmaPhase);
1874 (this->m_amr)->interpGhostPwl(this->m_electricFieldParticle, this->m_particleRealm, this->m_plasmaPhase);
1875 (this->m_amr)->interpToCentroids(this->m_electricFieldParticle, this->m_particleRealm, this->m_plasmaPhase);
1876
1877 return converged;
1878}
1879
1880template <typename I, typename C, typename R, typename F>
1881void
1883{
1884 CH_TIME("ItoKMCGodunovStepper::advanceEulerMaruyama");
1885 if (this->m_verbosity > 5) {
1886 pout() << this->m_name + "::advanceEulerMaruyama" << endl;
1887 }
1888
1889 // Store X^k positions.
1890 this->setOldPositions();
1891
1892 // Compute the explicit displacement -- the diffusion hop plus the lagged dt*grad(D) drift. This copies onto
1893 // m_rhoDaggerParticles and stores the displacement on the full particles. We need to remap the particle
1894 // species that moved.
1895 this->barrier();
1896 m_timer.startEvent("Diffuse particles");
1897 this->diffuseParticlesEulerMaruyama(m_rhoDaggerParticles, a_dt);
1898 this->remapPointParticles(m_rhoDaggerParticles, SpeciesSubset::ChargedDiffusive);
1899 m_timer.stopEvent("Diffuse particles");
1900
1901 // Perform the diffusive CDR advance.
1902 this->barrier();
1903 m_timer.startEvent("Diffuse CDR");
1904 this->computeDiffusionTermCDR(m_semiImplicitRhoCDR, a_dt);
1905 m_timer.stopEvent("Diffuse CDR");
1906
1907 // Compute the conductivity on the mesh. This deposits q_e * Z * w * mu on the mesh.
1908 this->barrier();
1909 m_timer.startEvent("Compute conductivities");
1910 this->copyConductivityParticles(m_conductivityParticles);
1911 this->computeConductivities(m_conductivityParticles, false);
1912 m_timer.stopEvent("Compute conductivities");
1913
1914 // Set up the semi-implicit Poisson solver with the computed conductivities.
1915 this->barrier();
1916 m_timer.startEvent("Setup Poisson");
1917 this->setupSemiImplicitPoisson(a_dt);
1918 m_timer.stopEvent("Setup Poisson");
1919
1920 // Compute space charge density arising from the displaced particle positions X^k + sqrt(2*D*dt)*W + dt*grad(D).
1921 // Only need to do the diffusive and charged species.
1922 this->barrier();
1923 m_timer.startEvent("Deposit point particles");
1924 this->depositPointParticles(m_rhoDaggerParticles, SpeciesSubset::Charged);
1925 this->computeSemiImplicitRho();
1926 m_timer.stopEvent("Deposit point particles");
1927
1928 // Solve the semi-implicit Poisson equation.
1929 this->barrier();
1930 m_timer.startEvent("Solve Poisson");
1931 const bool converged = this->solvePoisson();
1932 if (!converged) {
1933 const std::string errMsg = "ItoKMCGodunovStepper::advanceEulerMaruyama - Poisson solve did not converge";
1934
1935 pout() << errMsg << endl;
1936
1937 if (this->m_abortOnFailure) {
1938 MayDay::Error(errMsg.c_str());
1939 }
1940 }
1941 m_timer.stopEvent("Solve Poisson");
1942
1943 // Recompute velocities with the new electric field. This interpolates the velocities to the current particle
1944 // positions, i.e. we compute V^(k+1)(X^k) = mu^k * E^(k+1)(X^k)
1945 this->barrier();
1946 m_timer.startEvent("Step-compute v");
1947#if 1 // This is what the algorithm says.
1948 this->setCdrVelocityFunctions();
1949 this->setItoVelocityFunctions();
1950 (this->m_ito)->interpolateVelocities();
1951 this->multiplyCdrVelocitiesByMobilities();
1952#else // Have to use this for LEA - need to debug.
1953 this->computeDriftVelocities();
1954#endif
1955 m_timer.stopEvent("Step-compute v");
1956
1957 // Finalize the Euler-Maruyama update.
1958 this->barrier();
1959 m_timer.startEvent("Euler-Maruyama step");
1960 this->stepEulerMaruyamaParticles(a_dt);
1961 this->remapParticles(SpeciesSubset::AllMobileOrDiffusive);
1962 this->stepEulerMaruyamaCDR(a_dt);
1963 m_timer.stopEvent("Euler-Maruyama step");
1964}
1965
1966template <typename I, typename C, typename R, typename F>
1967void
1969 Vector<RefCountedPtr<ParticleContainer<NoPayload>>>& a_rhoDaggerParticles,
1970 const Real a_dt) noexcept
1971{
1972 CH_TIME("ItoKMCGodunovStepper::diffuseParticlesEulerMaruyama");
1973 if (this->m_verbosity > 5) {
1974 pout() << this->m_name + "::diffuseParticlesEulerMaruyama" << endl;
1975 }
1976
1977 this->clearPointParticles(a_rhoDaggerParticles, SpeciesSubset::All);
1978
1979 // Hops that would have carried a particle, or its charge, inside the boundary and were corrected instead.
1980 long long numCrossings = 0;
1981 long long numTotalTested = 0;
1982 long long numReflectFailed = 0;
1983
1984 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
1985 RefCountedPtr<ItoSolver>& solver = solverIt();
1986 const RefCountedPtr<ItoSpecies>& species = solver->getSpecies();
1987
1988 const int idx = solverIt.index();
1989
1990 const bool mobile = solver->isMobile();
1991 const bool diffusive = solver->isDiffusive();
1992 const int Z = species->getChargeNumber();
1993
1994 const auto& diffusionFunction = (this->m_physics)->getItoDiffusionFunctions()[idx];
1995
1996 // The grad(D) drift correction. An Ito-interpretation Euler-Maruyama update with a drift of v alone
1997 // transports the flux v*n - grad(D*n); the drift has to be v + grad(D) for the particles to transport
1998 // v*n - D*grad(n), which is what CdrSolver integrates.
1999 //
2000 // The correction belongs here, with the hop, and not in the particle velocities: it is lagged and
2001 // independent of E^(k+1), so it goes into the explicit half of the semi-implicit split, where the
2002 // rho^dagger deposit accounts for it exactly. Putting it in the velocities would add a term that is not
2003 // proportional to E^(k+1) to the implicit half, which is the half the semi-implicit Poisson operator was
2004 // built for. This mirrors what computeDiffusionTermCDR does with the explicit dt*div(D*grad(phi^k)).
2005 const bool gradientDrift = diffusive && solver->isDiffusionGradientDrift();
2006
2007 if (gradientDrift) {
2008 solver->computeDiffusionGradient();
2009 }
2010
2011 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
2012 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
2013 const DataIterator& dit = dbl.dataIterator();
2014
2015 // For the boundary test on the diffusion hop below. The intersection algorithm and its path-march length come
2016 // from the solver that owns the particles, so that the crossing point reflection mirrors about and the one the
2017 // absorption test finds later in the advance are located the same way.
2018 const RefCountedPtr<BaseIF>& baseif = (this->m_amr)->getBaseImplicitFunction(this->m_plasmaPhase);
2019 const Real dx = (this->m_amr)->getDx()[lvl];
2020 const EBIntersection intersectionAlg = solver->getIntersectionAlgorithm();
2021 const Real bisectStep = solver->getBisectionStep();
2022
2023 const bool checkEB = (m_diffusiveDeposit != DiffusiveDeposit::Inside) && !baseif.isNull();
2024
2025 // ReflectIto is the one setting that corrects the particle's displacement rather than only its deposit.
2026 const bool correctTrajectory = (m_diffusiveDeposit == DiffusiveDeposit::ReflectIto);
2027
2028 auto& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk)[lvl];
2029
2030 const int nbox = dit.size();
2031
2032#pragma omp parallel for schedule(runtime) reduction(+ : numCrossings, numTotalTested, numReflectFailed)
2033 for (int mybox = 0; mybox < nbox; mybox++) {
2034 const DataIndex& din = dit[mybox];
2035
2036 ParticleSoA<ItoParticle>& leaf = particles[din];
2037 ParticleSoA<NoPayload>& pointParticles = (*a_rhoDaggerParticles[idx])[lvl][din];
2038
2039 // A depositing species produces exactly one point particle per particle -- in the first pass if its
2040 // displacement stayed in the fluid, otherwise in one of the two correction passes below, never both.
2041 // The count is therefore known here, and reserving it keeps the appends from walking the doubling
2042 // ladder: growing from 16 to a patch's population copies the arena roughly twice over, every step.
2043 if (Z != 0) {
2044 pointParticles.reserve(pointParticles.size() + leaf.size());
2045 }
2046
2047 // Puts (grad D)(X_p) on the scratch vector columns. Those columns are also where the total explicit
2048 // displacement ends up, so each particle's gradient is read into a local below before being
2049 // overwritten, within the same loop iteration.
2050 if (gradientDrift) {
2051 solver->interpolateDiffusionGradient(lvl, din);
2052 }
2053
2054 // The trajectory and the deposit are corrected independently, and are only the same correction when the
2055 // deposit carries the hop. Two crossing lists rather than one, because a particle can cross on one
2056 // displacement and not the other: with the hop left out of rho^dagger the deposit moves by dt*grad(D)
2057 // alone, which can land inside the solid on its own or fail to when the full hop would have. Only the
2058 // crossings are carried, so both stay empty -- and unallocated -- for every box away from the geometry.
2059 const bool depositFollowsHop = correctTrajectory && m_rhoDaggerHop;
2060
2061 // Particles whose own displacement crossed. Filled only when the trajectory is corrected.
2062 std::vector<std::size_t> hopCrossings;
2063 std::vector<Real> hopCrossingsFOld;
2064
2065 // Charged particles whose DEPOSIT displacement crossed, when the deposit does not simply follow the
2066 // corrected trajectory. Carries that displacement, which is not recoverable from the scratch columns.
2067 std::vector<std::size_t> depositCrossings;
2068 std::vector<Real> depositCrossingsFOld;
2069 std::vector<RealVect> depositCrossingsDisp;
2070
2071 // First pass: the displacement for every particle, and the cheap test for whether it crossed.
2072 for (std::size_t i = 0; i < leaf.size(); i++) {
2073 const Real weight = leaf.weight(i);
2074 const RealVect pos = leaf.position(i);
2075
2076 // Compute a particle hop and store it on the run-time storage. The DiffusionFunction reads payload
2077 // columns (diffusion/velocity/scratch), so materialize the payload via gather(). Note that the
2078 // scratch vector columns hold grad(D) at this point, which a custom diffusion model may use.
2079 const ItoParticle p = leaf.gather(i);
2080 const RealVect hop = diffusive ? diffusionFunction(p, a_dt) : RealVect::Zero;
2081
2082 const RealVect gradD = gradientDrift ? RealVect(D_DECL(p.scratch_x, p.scratch_y, p.scratch_z))
2083 : RealVect::Zero;
2084
2085 // The total explicit displacement: the stochastic hop plus the lagged grad(D) drift. Under ReflectIto the
2086 // second pass may correct this in place; under every other setting it is already final and the boundary
2087 // handling touches only where the charge is deposited.
2088 const RealVect disp = hop + a_dt * gradD;
2089
2090 D_DECL(leaf.template get<&ItoParticle::scratch_x>(i) = static_cast<ParticleReal>(disp[0]),
2091 leaf.template get<&ItoParticle::scratch_y>(i) = static_cast<ParticleReal>(disp[1]),
2092 leaf.template get<&ItoParticle::scratch_z>(i) = static_cast<ParticleReal>(disp[2]));
2093
2094 // What the rho^dagger deposit displaces by. The hop can be left out of it while the particle still takes
2095 // it; the grad(D) drift never is, being deterministic mean transport that the explicit half must account
2096 // for.
2097 const RealVect depositDisp = m_rhoDaggerHop ? disp : a_dt * gradD;
2098
2099 const bool deposits = (Z != 0);
2100
2101 if (!checkEB) {
2102 if (deposits) {
2103 pointParticles.append(pos + depositDisp, weight);
2104 }
2105
2106 continue;
2107 }
2108
2109 // A sign CHANGE rather than a sign, so nothing here assumes which side the implicit function calls
2110 // positive. pos is known to be in the fluid because the particle is there now.
2111 const Real fOld = baseif->value(pos);
2112
2113 const auto crosses = [&](const RealVect& a_disp) -> bool {
2114 return fOld * baseif->value(pos + a_disp) < 0.0;
2115 };
2116
2117 // The trajectory correction, which only ReflectIto applies, and which applies to every species rather
2118 // than only those that deposit.
2119 bool hopCrossed = false;
2120
2121 if (correctTrajectory) {
2122 numTotalTested++;
2123
2124 hopCrossed = crosses(disp);
2125
2126 if (hopCrossed) {
2127 hopCrossings.push_back(i);
2128 hopCrossingsFOld.push_back(fOld);
2129 }
2130 }
2131
2132 if (!deposits) {
2133 continue;
2134 }
2135
2136 // The deposit. It follows the corrected trajectory when it carries the hop, and is otherwise tested and
2137 // corrected on its own displacement -- which is the case the trajectory test cannot stand in for.
2138 if (depositFollowsHop) {
2139 if (!hopCrossed) {
2140 pointParticles.append(pos + depositDisp, weight);
2141 }
2142 }
2143 else {
2144
2145 // A second, genuinely distinct test when the deposit does not carry the hop, so it counts separately.
2146 numTotalTested++;
2147
2148 if (crosses(depositDisp)) {
2149 depositCrossings.push_back(i);
2150 depositCrossingsFOld.push_back(fOld);
2151 depositCrossingsDisp.push_back(depositDisp);
2152 }
2153 else {
2154 pointParticles.append(pos + depositDisp, weight);
2155 }
2156 }
2157 }
2158
2159 // Second pass, trajectories: mirror the particle's own displacement about the boundary. Zero is
2160 // unambiguous -- a displacement that crossed cannot reflect to exactly no displacement, so it is the
2161 // routine reporting that it could not place the particle back in the fluid, and cancelling the hop is
2162 // always safe from a position known to be there.
2163 for (std::size_t k = 0; k < hopCrossings.size(); k++) {
2164 const std::size_t i = hopCrossings[k];
2165 const Real fOld = hopCrossingsFOld[k];
2166
2167 const RealVect pos = leaf.position(i);
2168 const RealVect disp = RealVect(D_DECL(leaf.template get<&ItoParticle::scratch_x>(i),
2169 leaf.template get<&ItoParticle::scratch_y>(i),
2170 leaf.template get<&ItoParticle::scratch_z>(i)));
2171
2172 numCrossings++;
2173
2174 const RealVect correctedDisp = this->reflectDiffusionHop(pos, disp, fOld, dx, intersectionAlg, bisectStep);
2175
2176 if (correctedDisp == RealVect::Zero) {
2177 numReflectFailed++;
2178 }
2179
2180 D_DECL(leaf.template get<&ItoParticle::scratch_x>(i) = static_cast<ParticleReal>(correctedDisp[0]),
2181 leaf.template get<&ItoParticle::scratch_y>(i) = static_cast<ParticleReal>(correctedDisp[1]),
2182 leaf.template get<&ItoParticle::scratch_z>(i) = static_cast<ParticleReal>(correctedDisp[2]));
2183
2184 // The deposit rides along when it carries the hop; otherwise it was handled on its own displacement.
2185 if (Z != 0 && depositFollowsHop) {
2186 pointParticles.append(pos + correctedDisp, leaf.weight(i));
2187 }
2188 }
2189
2190 // Second pass, deposits: create the point particle at the position it keeps. Cancel deposits at the
2191 // pre-hop position, which conserves the charge but biases the density towards where it started, and a
2192 // reflection that could not reach the fluid falls through to the same place.
2193 for (std::size_t k = 0; k < depositCrossings.size(); k++) {
2194 const std::size_t i = depositCrossings[k];
2195 const Real fOld = depositCrossingsFOld[k];
2196 const RealVect depositDisp = depositCrossingsDisp[k];
2197
2198 const RealVect pos = leaf.position(i);
2199
2200 numCrossings++;
2201
2202 RealVect correctedDisp = RealVect::Zero;
2203
2204 if (m_diffusiveDeposit != DiffusiveDeposit::Cancel) {
2205 correctedDisp = this->reflectDiffusionHop(pos, depositDisp, fOld, dx, intersectionAlg, bisectStep);
2206
2207 if (correctedDisp == RealVect::Zero) {
2208 numReflectFailed++;
2209 }
2210 }
2211
2212 pointParticles.append(pos + correctedDisp, leaf.weight(i));
2213 }
2214 }
2215 }
2216 }
2217
2218 // Reduced unconditionally: these are collectives and the condition below is not one.
2219 numCrossings = ParallelOps::sum(numCrossings);
2220 numTotalTested = ParallelOps::sum(numTotalTested);
2221 numReflectFailed = ParallelOps::sum(numReflectFailed);
2222
2223 if (this->m_verbosity > 5 && numTotalTested > 0) {
2224 const std::string what = (m_diffusiveDeposit == DiffusiveDeposit::ReflectIto) ? "particle hops"
2225 : "rho^dagger deposits";
2226
2227 pout() << this->m_name + "::diffuseParticlesEulerMaruyama - "
2228 << ((m_diffusiveDeposit == DiffusiveDeposit::Cancel) ? "cancelled " : "reflected ") << numCrossings << " of "
2229 << numTotalTested << " " << what << " (" << (100.0 * numCrossings) / numTotalTested
2230 << "%) that would have crossed the EB";
2231
2232 if (m_diffusiveDeposit != DiffusiveDeposit::Cancel) {
2233 pout() << "; " << numReflectFailed << " could not be reflected into the fluid and were cancelled";
2234 }
2235
2236 pout() << endl;
2237 }
2238}
2239
2240template <typename I, typename C, typename R, typename F>
2241void
2242ItoKMCGodunovStepper<I, C, R, F>::computeDiffusionTermCDR(EBAMRCellData& a_semiImplicitRhoCDR, const Real a_dt) noexcept
2243{
2244 CH_TIME("ItoKMCGodunovStepper::diffuseCDREulerMaruyama");
2245 if (this->m_verbosity > 5) {
2246 pout() << this->m_name + "::diffuseCDREulerMaruyama" << endl;
2247 }
2248
2249 DataOps::setValue(a_semiImplicitRhoCDR, 0.0);
2250
2251 for (auto solverIt = this->m_cdr->iterator(); solverIt.ok(); ++solverIt) {
2252 const RefCountedPtr<CdrSolver>& solver = solverIt();
2253 const RefCountedPtr<CdrSpecies>& species = solver->getSpecies();
2254
2255 const int index = solverIt.index();
2256 const int Z = species->getChargeNumber();
2257
2258 const EBAMRCellData& phi = solver->getPhi();
2259
2260 // Compute finite-volume approximation to div(D*grad(phi))
2261 if (solver->isDiffusive()) {
2262 solver->computeDivD(m_cdrDivD[index], solver->getPhi(), false, false, false);
2263 }
2264
2265 // Update the space charge density arising from the update phi^dagger = phi^k + dt * div(D*grad(phi^k))
2266 if (Z != 0) {
2267 DataOps::incr(a_semiImplicitRhoCDR, phi, 1.0 * Z);
2268 if (solver->isDiffusive()) {
2269 DataOps::incr(a_semiImplicitRhoCDR, m_cdrDivD[index], 1.0 * Z * a_dt);
2270 }
2271 }
2272 }
2273
2274 DataOps::scale(a_semiImplicitRhoCDR, Units::Qe);
2275
2276 this->m_amr->arithmeticAverage(a_semiImplicitRhoCDR, this->m_fluidRealm, this->m_plasmaPhase);
2277 this->m_amr->interpGhostPwl(a_semiImplicitRhoCDR, this->m_fluidRealm, this->m_plasmaPhase);
2278}
2279
2280template <typename I, typename C, typename R, typename F>
2281void
2283{
2284 CH_TIME("ItoKMCGodunovStepper::stepEulerMaruyamaParticles");
2285 if (this->m_verbosity > 5) {
2286 pout() << this->m_name + "::stepEulerMaruyamaParticles" << endl;
2287 }
2288
2289 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
2290 RefCountedPtr<ItoSolver>& solver = solverIt();
2291
2292 const bool mobile = solver->isMobile();
2293 const bool diffusive = solver->isDiffusive();
2294
2295 const Real f = mobile ? a_dt : 0.0;
2296 const Real g = diffusive ? 1.0 : 0.0;
2297
2298 if (mobile || diffusive) {
2299 for (int lvl = 0; lvl <= (this->m_amr)->getFinestLevel(); lvl++) {
2300 const DisjointBoxLayout& dbl = (this->m_amr)->getGrids((this->m_particleRealm))[lvl];
2301 const DataIterator& dit = dbl.dataIterator();
2302
2303 auto& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk)[lvl];
2304
2305 const int nbox = dit.size();
2306
2307#pragma omp parallel for schedule(runtime)
2308 for (int mybox = 0; mybox < nbox; mybox++) {
2309 const DataIndex& din = dit[mybox];
2310
2311 ParticleSoA<ItoParticle>& leaf = particles[din];
2312
2313 double* const pos[SpaceDim] = {
2314 D_DECL(leaf.positionColumn(0), leaf.positionColumn(1), leaf.positionColumn(2))};
2315 double* const oldPos[SpaceDim] = {D_DECL(leaf.template column<&ItoParticle::old_x>(),
2316 leaf.template column<&ItoParticle::old_y>(),
2317 leaf.template column<&ItoParticle::old_z>())};
2318 ParticleReal* const vel[SpaceDim] = {D_DECL(leaf.template column<&ItoParticle::vx>(),
2319 leaf.template column<&ItoParticle::vy>(),
2320 leaf.template column<&ItoParticle::vz>())};
2321 ParticleReal* const disp[SpaceDim] = {D_DECL(leaf.template column<&ItoParticle::scratch_x>(),
2322 leaf.template column<&ItoParticle::scratch_y>(),
2323 leaf.template column<&ItoParticle::scratch_z>())};
2324
2325 ParticleLoops::loop(leaf, [&](const std::size_t i) {
2326 // Add in the advective contribution and the explicit displacement (hop + dt*grad(D)).
2327 for (int dir = 0; dir < SpaceDim; dir++) {
2328 pos[dir][i] = oldPos[dir][i] + f * vel[dir][i] + g * disp[dir][i];
2329 }
2330 });
2331 }
2332 }
2333 }
2334 }
2335}
2336
2337template <typename I, typename C, typename R, typename F>
2338void
2340{
2341 CH_TIME("ItoKMCGodunovStepper::stepEulerMaruyamaCDR");
2342 if (this->m_verbosity > 5) {
2343 pout() << this->m_name + "::stepEulerMaruyamaCDR" << endl;
2344 }
2345
2346 for (auto solverIt = (this->m_cdr)->iterator(); solverIt.ok(); ++solverIt) {
2347 RefCountedPtr<CdrSolver>& solver = solverIt();
2348
2349 const int index = solverIt.index();
2350
2351 EBAMRCellData& phi = solver->getPhi();
2352
2353 // A diffusive solver arrives here already synchronized and does not pay for it again. Its coarse valid
2354 // cells were averaged after the last write to phi -- by coarsenCDRSolvers() at the end of the reaction
2355 // advance, or by resolveSecondaryEmissionEB(), or by CdrSolver::regrid()/initialData() on the entry
2356 // paths -- and its ghost cells were filled earlier in THIS step by CdrMultigrid::computeDivD(), which
2357 // interpolates them itself before differencing. Nothing writes phi in between. A solver that is mobile
2358 // but not diffusive gets no computeDivD, so it still needs the fill before computeDivF reads across
2359 // patch boundaries.
2360 if (!solver->isDiffusive()) {
2361 this->m_amr->conservativeAverage(phi, this->m_fluidRealm, this->m_plasmaPhase);
2362 this->m_amr->interpGhostPwl(phi, this->m_fluidRealm, this->m_plasmaPhase);
2363 }
2364
2365 // Add in the advective term -- BC comes later.
2366 if (solver->isMobile()) {
2367 DataOps::setValue(solver->getEbFlux(), 0.0);
2368
2369 // Compute the FV approximation to the advective term and do the Euler advance. If the underlying
2370 // CDR solver is a CTU solver, we also add in the transverse terms.
2371 solver->computeDivF(this->m_fluidScratch1, phi, a_dt, false, true, true);
2372
2373 DataOps::incr(phi, this->m_fluidScratch1, -a_dt);
2374 }
2375
2376 // Add in the diffusion term.
2377 if (solver->isDiffusive()) {
2378 DataOps::incr(phi, this->m_cdrDivD[index], a_dt);
2379 }
2380
2381 DataOps::floor(phi, 0.0, this->m_amr->getVofIterator(this->m_fluidRealm, this->m_plasmaPhase));
2382 }
2383
2384 this->coarsenCDRSolvers(true);
2385}
2386
2387#ifdef CH_USE_HDF5
2388template <typename I, typename C, typename R, typename F>
2389void
2390ItoKMCGodunovStepper<I, C, R, F>::writeCheckpointHeader(HDF5HeaderData& a_header) const noexcept
2391{
2392 CH_TIME("ItoKMCGodunovStepper::writeCheckpointHeader");
2393 if (this->m_verbosity > 5) {
2394 pout() << this->m_name + "::writeCheckpointHeader" << endl;
2395 }
2396
2397 a_header.m_real["prev_dt"] = this->m_prevDt;
2398 a_header.m_real["physics_dt"] = this->m_physicsDt;
2399 a_header.m_int["checkpoint_particles"] = m_writeCheckpointParticles ? 1 : 0;
2400}
2401#endif
2402
2403#ifdef CH_USE_HDF5
2404template <typename I, typename C, typename R, typename F>
2405void
2406ItoKMCGodunovStepper<I, C, R, F>::readCheckpointHeader(HDF5HeaderData& a_header) noexcept
2407{
2408 CH_TIME("ItoKMCGodunovStepper::readCheckpointHeader");
2409 if (this->m_verbosity > 5) {
2410 pout() << this->m_name + "::readCheckpointHeader" << endl;
2411 }
2412
2413 this->m_prevDt = a_header.m_real["prev_dt"];
2414 this->m_physicsDt = a_header.m_real["physics_dt"];
2415
2416 m_readCheckpointParticles = (a_header.m_int["checkpoint_particles"] != 0) ? true : false;
2417 m_canRegridOnRestart = m_readCheckpointParticles;
2418}
2419#endif
2420
2421#ifdef CH_USE_HDF5
2422template <typename I, typename C, typename R, typename F>
2423void
2424ItoKMCGodunovStepper<I, C, R, F>::writeCheckpointData(HDF5Handle& a_handle, const int a_lvl) const noexcept
2425{
2426 CH_TIME("ItoKMCGodunovStepper::writeCheckpointData");
2427 if (this->m_verbosity > 5) {
2428 pout() << this->m_name + "::writeCheckpointData" << endl;
2429 }
2430
2432
2433 // Write the point-particles.
2434 if (m_writeCheckpointParticles) {
2435 for (int i = 0; i < (this->m_physics)->getNumItoSpecies(); i++) {
2436 const std::string identifierSigma = "ItoKMCGodunovStepper::conductivityParticles_" + std::to_string(i);
2437 const std::string identifierRho = "ItoKMCGodunovStepper::spaceChargeParticles_" + std::to_string(i);
2438
2439 const ParticleContainer<NoPayload>& conductivityParticles = *m_conductivityParticles[i];
2440 const ParticleContainer<NoPayload>& rhoDaggerParticles = *m_rhoDaggerParticles[i];
2441
2442 DischargeIO::writeCheckParticlesToHDF(a_handle, conductivityParticles[a_lvl], identifierSigma);
2443 DischargeIO::writeCheckParticlesToHDF(a_handle, rhoDaggerParticles[a_lvl], identifierRho);
2444 }
2445 }
2446
2447 // Write data required for the semi-implicit regrid.
2448 if (this->m_physics->getNumCdrSpecies() > 0) {
2449 write(a_handle, *m_semiImplicitRhoCDR[a_lvl], "ItoKMCGodunovStepper::semiImplicitRhoCDR");
2450 write(a_handle, *m_semiImplicitConductivityCDR[a_lvl], "ItoKMCGodunovStepper::semiImplicitConductivityCDR");
2451 }
2452}
2453#endif
2454
2455#ifdef CH_USE_HDF5
2456template <typename I, typename C, typename R, typename F>
2457void
2458ItoKMCGodunovStepper<I, C, R, F>::readCheckpointData(HDF5Handle& a_handle, const int a_lvl) noexcept
2459{
2460 CH_TIME("ItoKMCGodunovStepper::readCheckpointData");
2461 if (this->m_verbosity > 5) {
2462 pout() << this->m_name + "::readCheckpointData" << endl;
2463 }
2464
2466
2467 // Write the point-particles.
2468 if (m_readCheckpointParticles) {
2469 for (int i = 0; i < (this->m_physics)->getNumItoSpecies(); i++) {
2470 const std::string identifierSigma = "ItoKMCGodunovStepper::conductivityParticles_" + std::to_string(i);
2471 const std::string identifierRho = "ItoKMCGodunovStepper::spaceChargeParticles_" + std::to_string(i);
2472
2473 ParticleContainer<NoPayload>& conductivityParticles = *m_conductivityParticles[i];
2474 ParticleContainer<NoPayload>& rhoDaggerParticles = *m_rhoDaggerParticles[i];
2475
2476 DischargeIO::readCheckParticlesFromHDF(a_handle, conductivityParticles[a_lvl], identifierSigma);
2477 DischargeIO::readCheckParticlesFromHDF(a_handle, rhoDaggerParticles[a_lvl], identifierRho);
2478 }
2479 }
2480
2481 // Read in data that is required for the semi-implicit regrid.
2482 if (this->m_physics->getNumCdrSpecies() > 0) {
2483 const Interval interv(0, 0);
2484
2485 read<EBCellFAB>(a_handle,
2486 *m_semiImplicitRhoCDR[a_lvl],
2487 "ItoKMCGodunovStepper::semiImplicitRhoCDR",
2488 this->m_amr->getGrids(this->m_fluidRealm)[a_lvl],
2489 interv,
2490 false);
2491
2492 read<EBCellFAB>(a_handle,
2493 *m_semiImplicitConductivityCDR[a_lvl],
2494 "ItoKMCGodunovStepper::semiImplicitConductivityCDR",
2495 this->m_amr->getGrids(this->m_fluidRealm)[a_lvl],
2496 interv,
2497 false);
2498 }
2499}
2500#endif
2501
2502template <typename I, typename C, typename R, typename F>
2503void
2505{
2506 CH_TIME("ItoKMCGodunovStepper::prePlot");
2507 if (this->m_verbosity > 5) {
2508 pout() << "ItoKMCGodunovStepper::prePlot" << endl;
2509 }
2510
2512
2513 // Deposit the photons that were absorbed on the mesh during the last advance. This used to run at the top
2514 // of every advance(); it belongs here because McPhoto's mesh density has exactly two readers, both of them
2515 // output paths (McPhoto::writePlotData and McPhoto::writeCheckpointLevel), and a full deposit is a halo
2516 // exchange plus a hybrid-divergence redistribution per photon species. The checkpoint writer has no
2517 // pre-write hook, so a checkpoint written on a non-plot step now records the density from the last plot
2518 // rather than from the last step -- nothing reads that field back on restart, where the photons are
2519 // restored from their own particle datasets.
2520 for (auto solverIt = (this->m_rte)->iterator(); solverIt.ok(); ++solverIt) {
2521 RefCountedPtr<McPhoto> solver = solverIt();
2522
2523 EBAMRCellData& phi = solver->getPhi();
2524 ParticleContainer<Photon>& photons = solver->getBulkPhotons();
2525
2526 solver->depositPhotons(phi, photons, DepositionType::NGP);
2527 }
2528}
2529
2530template <typename I, typename C, typename R, typename F>
2531void
2533{
2534 CH_TIME("ItoKMCGodunovStepper::postPlot");
2535 if (this->m_verbosity > 5) {
2536 pout() << this->m_name + "::postPlot" << endl;
2537 }
2538
2539 this->m_physicsPlotVariables.clear();
2540
2541 this->plotParticles();
2542}
2543
2544template <typename I, typename C, typename R, typename F>
2545void
2547{
2548 CH_TIME("ItoKMCGodunovStepper::plotParticles");
2549 if (this->m_verbosity > 2) {
2550 pout() << this->m_name + "::plotParticles" << endl;
2551 }
2552
2553 bool plotParticles = false;
2554
2555 ParmParse pp(this->m_name.c_str());
2556
2557 pp.query("plot_particles", plotParticles);
2558
2559 if (plotParticles) {
2560
2561 for (auto solverIt = (this->m_ito)->iterator(); solverIt.ok(); ++solverIt) {
2562 const RefCountedPtr<ItoSolver>& solver = solverIt();
2563 const ParticleContainer<ItoParticle>& particles = solver->getParticles(ItoSolver::WhichContainer::Bulk);
2564
2565 // Create the output folder
2566 // Quoted: a solver name containing a space would otherwise make the shell create one directory
2567 // per word, and the file open then fails with a bare MPI_ERR_NO_SUCH_FILE from inside HDF5.
2568 std::string cmd = "mkdir -p 'particles/" + solver->getName() + "'";
2569 int success = 0;
2570 if (procID() == 0) {
2571 success = system(cmd.c_str());
2572 }
2573
2574 if (success != 0) {
2575 MayDay::Error("ItoKMCGodunovStepper::plotParticles - could not create 'particles' directory");
2576 }
2577
2578 // Set plot file name
2579 const std::string prefix = "./particles/" + solver->getName() + "/" + solver->getName();
2580 char fileChar[1000];
2581 sprintf(fileChar, "%s.step%07d.%dd.h5part", prefix.c_str(), this->m_timeStep, SpaceDim);
2582
2583 // Plot the particles directly from the live SoA container. The output datasets (id, position, weight
2584 // and the selected ItoParticle payload columns) are derived from ParticleTraits<ItoParticle>::h5PartColumns.
2585 DischargeIO::writeH5Part(std::string(fileChar), particles, this->m_amr->getProbLo(), this->m_time);
2586 }
2587 }
2588}
2589
2590#include <CD_NamespaceFooter.H>
2591
2592#endif
Average
Various averaging methods.
Definition CD_Average.H:25
Agglomeration of useful data operations.
Silly, but useful functions that override standard Chombo HDF5 IO.
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
@ NGP
Put the particle's entire cloud in its own cell when that cell is a cut cell.
Declaration of a class which uses a semi-implicit Godunov method for Ito plasma equations.
SpeciesSubset
Enum for selecting a subset of plasma species by mobility/diffusion/charge properties.
Definition CD_ItoKMCStepper.H:43
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
SoA payload for Monte Carlo radiative-transfer photons.
Implementation of CD_Timer.H.
Declaration of various useful units.
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 filterSmooth(EBAMRCellData &a_data, const Real a_alpha, const int a_stride, const bool a_zeroEB) noexcept
Apply a convolved filter phi = alpha * phi_i + 0.5*(1-alpha) * [phi_(i+s) + phi_(i-s)] in each direct...
Definition CD_DataOps.cpp:680
static void multiply(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs)
Multiply data holder by another data holder.
Definition CD_DataOps.cpp:2307
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 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
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
RealVect getProbLo() const
Lower-left corner of the physical domain.
Definition CD_ParticleContainer.H:280
AMRParticlesSoA< P, Traits > & getParticles()
The valid particles on all levels.
Definition CD_ParticleContainer.H:317
static bool ebIntersectionRaycast(const RefCountedPtr< BaseIF > &a_impFunc, const RealVect &a_oldPos, const RealVect &a_newPos, const Real &a_tolerance, Real &a_s)
Compute the intersection point between a particle path and an implicit function using a ray-casting a...
Definition CD_ParticleOpsImplem.H:224
static bool ebIntersectionBisect(const RefCountedPtr< BaseIF > &a_impFunc, const RealVect &a_oldPos, const RealVect &a_newPos, const Real &a_bisectStep, Real &a_s)
Compute the intersection point between a particle path and an implicit function using a bisection alg...
Definition CD_ParticleOpsImplem.H:175
static void setData(ParticleContainer< P, Traits > &a_particles, const std::function< void(ParticleSoA< P, Traits > &, std::size_t)> &a_functor) noexcept
Set value function for SoA containers. Lets the user set particle parameters via a (leaf,...
Definition CD_ParticleOpsImplem.H:334
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
void append(const RealVect &a_position, const double a_weight)
Append one particle with a default-constructed payload.
Definition CD_ParticleSoA.H:962
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
P gather(const std::size_t a_index) const
Gather particle i's payload back into the AoS payload view.
Definition CD_ParticleSoA.H:1028
void catenate(ParticleSoA &a_other)
Move every particle of another container into this one, leaving a_other empty (catenate).
Definition CD_ParticleSoA.H:1009
void remove(const std::size_t a_index) noexcept
Remove particle i using swap-and-pop (O(1), does NOT preserve order).
Definition CD_ParticleSoA.H:1040
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
Implementation of ItoKMCStepper that uses a semi-implicit split-step formalism for advancing the Ito-...
Definition CD_ItoKMCGodunovStepper.H:32
virtual void setupSemiImplicitPoisson(const Real a_dt) noexcept
Set up the semi-implicit Poisson solver.
Definition CD_ItoKMCGodunovStepperImplem.H:1708
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_ItoKMCGodunovStepperImplem.H:900
virtual void computeDiffusionTermCDR(EBAMRCellData &m_semiImplicitRhoCDR, const Real a_dt) noexcept
Compute the diffusion term for the CDR equations as well as the resulting CDR-contributions to the sp...
Definition CD_ItoKMCGodunovStepperImplem.H:2242
virtual void allocate() noexcept override
Allocate storage required for advancing the equations.
Definition CD_ItoKMCGodunovStepperImplem.H:101
bool m_readCheckpointParticles
If true, then the HDF5 checkpoint file contained particles that we can read.
Definition CD_ItoKMCGodunovStepper.H:222
@ Inside
Deposit from inside the solid, into a covered cell that contributes nothing to rho.
virtual Real advance(const Real a_dt) override
Advance the Ito-Poisson-KMC system over a_dt.
Definition CD_ItoKMCGodunovStepperImplem.H:529
virtual void allocateInternals() noexcept override
Allocate "internal" storage.
Definition CD_ItoKMCGodunovStepperImplem.H:134
Real m_maxFieldAbort
Limit for maximum field abort.
Definition CD_ItoKMCGodunovStepper.H:288
virtual void stepEulerMaruyamaCDR(const Real a_dt) noexcept
Step the CDR equations according to the regular Euler-Maruyama scheme.
Definition CD_ItoKMCGodunovStepperImplem.H:2339
DiffusiveDeposit m_diffusiveDeposit
What to do with a rho^dagger deposit that would land inside the embedded boundary....
Definition CD_ItoKMCGodunovStepper.H:243
virtual void computeConductivities(const Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const bool a_useStoredCdrConductivity) noexcept
Compute all conductivities (cell, face, and EB) from the input point particles.
Definition CD_ItoKMCGodunovStepperImplem.H:1430
virtual void parseAlgorithm() noexcept
Parse advancement algorithm.
Definition CD_ItoKMCGodunovStepperImplem.H:236
virtual void clearPointParticles(const Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const SpeciesSubset a_subset) noexcept
Clear the input particle data holders.
Definition CD_ItoKMCGodunovStepperImplem.H:1294
virtual void postPlot() noexcept override
Perform post-plot operations.
Definition CD_ItoKMCGodunovStepperImplem.H:2532
virtual Real computeDt() override
Compute a time step used for the advance method.
Definition CD_ItoKMCGodunovStepperImplem.H:509
virtual void diffuseParticlesEulerMaruyama(Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_rhoDaggerParticles, const Real a_dt) noexcept
Perform the explicit half of the Ito advance in the Euler-Maruyama step.
Definition CD_ItoKMCGodunovStepperImplem.H:1968
virtual void parseSecondaryEmissionSpecification() noexcept
Parse when secondary particles are emitted.
Definition CD_ItoKMCGodunovStepperImplem.H:311
virtual void parseFiltering() noexcept
Parse filter settings.
Definition CD_ItoKMCGodunovStepperImplem.H:261
virtual void parseRuntimeOptions() noexcept override
Parse run-time options.
Definition CD_ItoKMCGodunovStepperImplem.H:216
virtual void depositPointParticles(const Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const SpeciesSubset a_subset) noexcept
Deposit the input point particles on the mesh.
Definition CD_ItoKMCGodunovStepperImplem.H:1192
virtual bool solvePoisson() noexcept override
Solve the electrostatic problem.
Definition CD_ItoKMCGodunovStepperImplem.H:1843
bool m_canRegridOnRestart
If true, then the class supports regrid-on-restart.
Definition CD_ItoKMCGodunovStepper.H:228
virtual void computeFaceConductivity() noexcept
Compute the cell-centered conductivity.
Definition CD_ItoKMCGodunovStepperImplem.H:1531
virtual void computeCellConductivity(EBAMRCellData &a_conductivityCell, const Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const bool a_useStoredCdrConductivity) noexcept
Compute the cell-centered conductivity.
Definition CD_ItoKMCGodunovStepperImplem.H:1445
virtual void removeCoveredPointParticles(Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const EBRepresentation a_representation, const Real a_tolerance) const noexcept
Remove covered particles.
Definition CD_ItoKMCGodunovStepperImplem.H:1745
bool m_rhoDaggerHop
If true, the stochastic diffusion hop enters rho^dagger along with the rest of the explicit displacem...
Definition CD_ItoKMCGodunovStepper.H:217
virtual void remapPointParticles(Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_particles, const SpeciesSubset a_subset) noexcept
Remap the input point particles.
Definition CD_ItoKMCGodunovStepperImplem.H:1087
virtual void prePlot() noexcept override
Perform pre-plot operations.
Definition CD_ItoKMCGodunovStepperImplem.H:2504
virtual ~ItoKMCGodunovStepper()
Destructor. Does nothing.
Definition CD_ItoKMCGodunovStepperImplem.H:75
bool m_extendConductivityEB
For achieving a slightly smoother gradient in the conductivity near the EB.
Definition CD_ItoKMCGodunovStepper.H:233
virtual void setOldPositions() noexcept
Set the starting positions for the ItoSolver particles.
Definition CD_ItoKMCGodunovStepperImplem.H:1048
virtual void parseDiffusiveDeposit() noexcept
Parse what happens to a diffusion hop that would cross the embedded boundary.
Definition CD_ItoKMCGodunovStepperImplem.H:439
bool m_writeCheckpointParticles
If true, then the particles are checkpointed so we can regrid on checkpoint-restart.
Definition CD_ItoKMCGodunovStepper.H:207
virtual void parseRhoDaggerHop() noexcept
Parse whether the stochastic diffusion hop enters rho^dagger.
Definition CD_ItoKMCGodunovStepperImplem.H:475
virtual void computeSemiImplicitRho() noexcept
Set up the space charge density for the regrid operation.
Definition CD_ItoKMCGodunovStepperImplem.H:1566
virtual void advanceEulerMaruyama(const Real a_dt) noexcept
Advance the particles using the Euler-Maruyama scheme.
Definition CD_ItoKMCGodunovStepperImplem.H:1882
virtual void preRegrid(const int a_lmin, const int a_oldFinestLevel) noexcept override
Perform pre-regrid operations.
Definition CD_ItoKMCGodunovStepperImplem.H:861
virtual void registerOperators() noexcept override
Register operators used for the simulation.
Definition CD_ItoKMCGodunovStepperImplem.H:85
virtual RealVect reflectDiffusionHop(const RealVect &a_pos, const RealVect &a_disp, const Real a_fOld, const Real a_dx, const EBIntersection a_intersectionAlg, const Real a_bisectStep) const noexcept
Reflect a rho^dagger deposit displacement that crosses the embedded boundary back into the fluid.
Definition CD_ItoKMCGodunovStepperImplem.H:342
virtual void plotParticles() const noexcept
Utility function for plotting the ItoSolver particles. These are written in a particles folder.
Definition CD_ItoKMCGodunovStepperImplem.H:2546
ItoKMCGodunovStepper()=delete
Disallowed default constructor. Use the full constructor.
virtual void parseReactiveFieldCentering() noexcept
Parse the time-centering of the electric field used for the reactive substep.
Definition CD_ItoKMCGodunovStepperImplem.H:489
virtual void parseCheckpointParticles() noexcept
Parse checkpoint-restart functionality.
Definition CD_ItoKMCGodunovStepperImplem.H:297
virtual void barrier() const noexcept
Set an MPI barrier if using debug mode.
Definition CD_ItoKMCGodunovStepperImplem.H:182
virtual void parseOptions() noexcept override
Parse options.
Definition CD_ItoKMCGodunovStepperImplem.H:196
virtual void computeCdrConductivity() noexcept
Compute the CDR contribution to the semi-implicit conductivity, i.e. sum(|Z| * phi * mu).
Definition CD_ItoKMCGodunovStepperImplem.H:1400
virtual void stepEulerMaruyamaParticles(const Real a_dt) noexcept
Step the particles according to the regular Euler-Maruyama scheme.
Definition CD_ItoKMCGodunovStepperImplem.H:2282
virtual void copyConductivityParticles(Vector< RefCountedPtr< ParticleContainer< NoPayload > > > &a_conductivityParticles) noexcept
Copy particles from the ItoSolver into PointParticles whose weight are ItoParticle::m_weight * ItoPar...
Definition CD_ItoKMCGodunovStepperImplem.H:1785
Abstract TimeStepper for the Ito-KMC-Poisson system of equations.
Definition CD_ItoKMCStepper.H:66
virtual void parseRuntimeOptions() noexcept override
Parse runtime configurable options.
Definition CD_ItoKMCStepperImplem.H:173
std::string m_name
Time stepper name.
Definition CD_ItoKMCStepper.H:402
virtual void registerOperators() noexcept override
Register operators used for the simulation.
Definition CD_ItoKMCStepperImplem.H:1648
virtual void parseOptions() noexcept
Parse options.
Definition CD_ItoKMCStepperImplem.H:152
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
Real m_prevDt
Previous time step.
Definition CD_ItoKMCStepper.H:541
virtual Real computeDt() override
Compute a time step used for the advance method.
Definition CD_ItoKMCStepperImplem.H:1519
virtual void allocateInternals() noexcept
Allocate "internal" storage.
Definition CD_ItoKMCStepperImplem.H:621
virtual void allocate() noexcept override
Allocate storage for solvers.
Definition CD_ItoKMCStepperImplem.H:603
virtual void prePlot() noexcept override
Perform pre-plot operations.
Definition CD_ItoKMCStepperImplem.H:1667
Class which is used for run-time monitoring of events.
Definition CD_Timer.H:32
void writeH5Part(std::string a_filename, const ParticleContainer< P, Traits > &a_particles, RealVect a_shift, Real a_time) noexcept
Write an SoA particle container to an H5Part file (quick visualization).
Definition CD_DischargeIOImplem.H:201
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354
void barrier() noexcept
MPI barrier.
Definition CD_ParallelOpsImplem.H:26
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
void deleteParticles(ParticleSoA< P, Traits > &a_particles, const Real a_weightThresh) noexcept
Remove particles from an SoA container if their weight is below the threshold (swap-and-pop).
Definition CD_ParticleManagementImplem.H:788
constexpr Real eps0
Permittivity of free space.
Definition CD_Units.H:30
constexpr Real Qe
Elementary charge.
Definition CD_Units.H:35
@ solid
Solid (dielectric) phase.
Definition CD_MultiFluidIndexSpace.H:40
@ gas
Gas phase.
Definition CD_MultiFluidIndexSpace.H:39
SoA payload for ItoSolver particles, i.e. drifting Brownian walkers.
Definition CD_ItoParticle.H:31
ParticleReal scratch_x
Scratch vector storage, x-component.
Definition CD_ItoParticle.H:49
ParticleReal scratch_y
Scratch vector storage, y-component.
Definition CD_ItoParticle.H:50
ParticleReal scratch_z
Scratch vector storage, z-component.
Definition CD_ItoParticle.H:52