chombo-discharge
Loading...
Searching...
No Matches
CD_DischargeInceptionStepperImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2022-2026 SINTEF Energy Research
3 * SPDX-FileCopyrightText: 2022-2026 NTNU
4 * SPDX-FileCopyrightText: 2022-2026 Fanny Skirbekk
5 *
6 * SPDX-License-Identifier: GPL-3.0-or-later
7 */
8
17#ifndef CD_DISCHARGEINCEPTIONSTEPPERIMPLEM_H
18#define CD_DISCHARGEINCEPTIONSTEPPERIMPLEM_H
19
20// Std includes
21#include <iostream>
22#include <fstream>
23
24// Chombo includes
25#include <CH_Timer.H>
26
27// Our includes
28#include <CD_Units.H>
29#include <CD_OpenMP.H>
30#include <CD_ParticleLoops.H>
33#include <CD_PolyUtils.H>
34#include <CD_NamespaceHeader.H>
35
36using namespace Physics::DischargeInception;
37
38template <typename P, typename F, typename C>
40{
41 CH_TIME("DischargeInceptionStepper::DischargeInceptionStepper");
42 if (m_verbosity > 5) {
43 pout() << "DischargeInceptionStepper::DischargeInceptionStepper" << endl;
44 }
45
46 // Default settings.
47 m_verbosity = -1;
48 m_realm = Realm::Primal;
49 m_phase = phase::gas;
50 m_mode = Mode::Stationary;
51 m_transportAlgorithm = TransportAlgorithm::ImExCTU;
52 m_timeStepRestriction = TimeStepRestriction::Unknown;
53 m_profile = false;
54 m_debug = false;
55 m_fullIntegration = false;
56 m_evaluateTownsend = false;
57 m_relativeDeltaU = 1 + 0.05;
58 m_deltaK = 1.0;
59 m_maxKLimit = 15.0;
60
61 this->parseOptions();
62
63 m_rho = [](const RealVect x) -> Real {
64 return 0.0;
65 };
66
67 m_sigma = [](const RealVect x) -> Real {
68 return 0.0;
69 };
70
71 m_alpha = [](const Real E, const RealVect x) -> Real {
72 return 0.0;
73 };
74
75 m_eta = [](const Real E, const RealVect x) -> Real {
76 return 0.0;
77 };
78
79 m_voltageCurve = [](const Real a_time) -> Real {
80 return 1.0;
81 };
82
83 m_backgroundRate = [](const Real E, const RealVect x) -> Real {
84 return 0.0;
85 };
86
87 m_detachmentRate = [](const Real E, const RealVect x) -> Real {
88 return 0.0;
89 };
90
91 m_fieldEmission = [](const Real E, const RealVect x) -> Real {
92 return 0.0;
93 };
94
95 m_secondaryEmission = [](const Real E, const RealVect x) -> Real {
96 return 0.0;
97 };
98
99 m_initialIonDensity = [](const RealVect x) -> Real {
100 return 0.0;
101 };
102
103 m_ionMobility = [](const Real E) -> Real {
104 return 0.0;
105 };
106
107 m_ionDiffusion = [](const Real E) -> Real {
108 return 0.0;
109 };
110}
111
112template <typename P, typename F, typename C>
114{
115 CH_TIME("DischargeInceptionStepper::~DischargeInceptionStepper");
116 if (m_verbosity > 5) {
117 pout() << "DischargeInceptionStepper::~DischargeInceptionStepper" << endl;
118 }
119}
120
121template <typename P, typename F, typename C>
122void
124{
125 CH_TIME("DischargeInceptionStepper::setupSolvers");
126 if (m_verbosity > 5) {
127 pout() << "DischargeInceptionStepper::setupSolvers" << endl;
128 }
129
130 // Always solve for the field using a voltage of one (and then scale up/down later on)
131 auto voltage = [](const Real a_time) -> Real {
132 return 1.0;
133 };
134
135 // Instantiate the field solver.
136 m_fieldSolver = RefCountedPtr<FieldSolver>(new F());
137 m_fieldSolver->setVerbosity(m_verbosity);
138 m_fieldSolver->parseOptions();
139 m_fieldSolver->setAmr(m_amr);
140 m_fieldSolver->setComputationalGeometry(m_computationalGeometry);
141 m_fieldSolver->setVoltage(voltage);
142 m_fieldSolver->setRealm(m_realm);
143 m_fieldSolver->setTime(0, 0.0, 0.0);
144
145 // Instantiate the tracer particle solver.
146 m_tracerParticleSolver = RefCountedPtr<TracerParticleSolver<P>>(new TracerParticleSolver<P>());
147 m_tracerParticleSolver->parseOptions();
148 m_tracerParticleSolver->setAmr(m_amr);
149 m_tracerParticleSolver->setComputationalGeometry(m_computationalGeometry);
150 m_tracerParticleSolver->setRealm(m_realm);
151 m_tracerParticleSolver->setPhase(m_phase);
152 m_tracerParticleSolver->setTime(0, 0.0, 0.0);
153 m_tracerParticleSolver->setName("Tracer particle solver");
154 m_tracerParticleSolver->setVolumeScale(true);
155 m_tracerParticleSolver->setDeposition(DepositionType::NGP);
156
157 // Instantiate the ion solver.
158 m_ionSolver = RefCountedPtr<CdrSolver>(new C());
159
160 m_ionSolver->parseOptions();
161 m_ionSolver->setPhase(m_phase);
162 m_ionSolver->setAmr(m_amr);
163 m_ionSolver->setComputationalGeometry(m_computationalGeometry);
164 m_ionSolver->setRealm(m_realm);
165
166 auto species = RefCountedPtr<CdrSpecies>(new DischargeInceptionSpecies(m_initialIonDensity, true, true));
167
168 m_ionSolver->setSpecies(species);
169}
170
171template <typename P, typename F, typename C>
172void
174{
175 CH_TIME("DischargeInceptionStepper::allocate");
176 if (m_verbosity > 5) {
177 pout() << "DischargeInceptionStepper::allocate" << endl;
178 }
179
180 m_fieldSolver->allocate();
181 m_tracerParticleSolver->allocate();
182 m_ionSolver->allocate();
183
184 // Needed for all modes.
185 m_amr->allocate(m_potential, m_realm, 1);
186 m_amr->allocate(m_potentialHomo, m_realm, 1);
187 m_amr->allocate(m_potentialInho, m_realm, 1);
188
189 m_amr->allocate(m_electricField, m_realm, SpaceDim);
190 m_amr->allocate(m_electricFieldHomo, m_realm, SpaceDim);
191 m_amr->allocate(m_electricFieldInho, m_realm, SpaceDim);
192
193 m_amr->allocate(m_gradAlpha, m_realm, m_phase, SpaceDim);
194
195 DataOps::setValue(m_potentialHomo, 0.0);
196 DataOps::setValue(m_potentialInho, 0.0);
197
198 switch (m_mode) {
199 case Mode::Stationary: {
200 m_amr->allocate(m_inceptionVoltagePlus, m_realm, m_phase, 1);
201 m_amr->allocate(m_inceptionVoltageMinu, m_realm, m_phase, 1);
202 m_amr->allocate(m_streamerInceptionVoltagePlus, m_realm, m_phase, 1);
203 m_amr->allocate(m_streamerInceptionVoltageMinu, m_realm, m_phase, 1);
204 m_amr->allocate(m_townsendInceptionVoltagePlus, m_realm, m_phase, 1);
205 m_amr->allocate(m_townsendInceptionVoltageMinu, m_realm, m_phase, 1);
206
207 DataOps::setValue(m_inceptionVoltagePlus, 0.0);
208 DataOps::setValue(m_inceptionVoltageMinu, 0.0);
209 DataOps::setValue(m_streamerInceptionVoltagePlus, 0.0);
210 DataOps::setValue(m_streamerInceptionVoltageMinu, 0.0);
211 DataOps::setValue(m_townsendInceptionVoltagePlus, 0.0);
212 DataOps::setValue(m_townsendInceptionVoltageMinu, 0.0);
213
214 break;
215 }
216 case Mode::Transient: {
217 m_amr->allocate(m_inceptionIntegral, m_realm, m_phase, 1);
218 m_amr->allocate(m_townsendCriterion, m_realm, m_phase, 1);
219 m_amr->allocate(m_emissionRate, m_realm, m_phase, 1);
220 m_amr->allocate(m_backgroundIonization, m_realm, m_phase, 1);
221 m_amr->allocate(m_detachment, m_realm, m_phase, 1);
222
223 DataOps::setValue(m_inceptionIntegral, 0.0);
224 DataOps::setValue(m_townsendCriterion, 0.0);
225 DataOps::setValue(m_emissionRate, 0.0);
226 DataOps::setValue(m_backgroundIonization, 0.0);
227 DataOps::setValue(m_detachment, 0.0);
228
229 break;
230 }
231 default: {
232 break;
233 }
234 }
235}
236
237template <typename P, typename F, typename C>
238void
240{
241 CH_TIME("DischargeInceptionStepper::initialData");
242 if (m_verbosity > 5) {
243 pout() << "DischargeInceptionStepper::initialData" << endl;
244 }
245
246 this->solvePoisson();
247}
248
249template <typename P, typename F, typename C>
250void
252{
253 CH_TIME("DischargeInceptionStepper::solvePoisson");
254 if (m_verbosity > 5) {
255 pout() << "DischargeInceptionStepper::solvePoisson" << endl;
256 }
257
258 // Solve for the inhomogeneous part
259 m_fieldSolver->setRho(m_rho);
260 m_fieldSolver->setSigma(m_sigma);
261 m_fieldSolver->setVoltage([](const Real& a_time) {
262 return 0.0;
263 });
264
265 // Solve using our m_potentialInho as an initial guess.
266 DataOps::copy(m_fieldSolver->getPotential(), m_potentialInho);
267 const bool convergedInho = m_fieldSolver->solve(m_fieldSolver->getPotential(),
268 m_fieldSolver->getRho(),
269 m_fieldSolver->getSigma(),
270 false);
271
272 if (!convergedInho) {
273 MayDay::Warning("DischargeInceptionStepper::solvePoisson -- could not solve the inhomogeneous Poisson equation. ");
274 }
275
276 DataOps::copy(m_potentialInho, m_fieldSolver->getPotential());
277 DataOps::copy(m_electricFieldInho, m_fieldSolver->getElectricField());
278
279 // Solve for the homogeneous part
280 m_fieldSolver->setRho([](const RealVect& a_pos) {
281 return 0.0;
282 });
283 m_fieldSolver->setSigma([](const RealVect& a_pos) {
284 return 0.0;
285 });
286 m_fieldSolver->setVoltage([](const Real& a_time) {
287 return 1.0;
288 });
289
290 DataOps::copy(m_fieldSolver->getPotential(), m_potentialHomo);
291 const bool convergedHomo = m_fieldSolver->solve(m_fieldSolver->getPotential(),
292 m_fieldSolver->getRho(),
293 m_fieldSolver->getSigma(),
294 false);
295
296 if (!convergedHomo) {
297 MayDay::Warning("DischargeInceptionStepper::solvePoisson -- could not solve the homogeneous Poisson equation. ");
298 }
299
300 DataOps::copy(m_potentialHomo, m_fieldSolver->getPotential());
301 DataOps::copy(m_electricFieldHomo, m_fieldSolver->getElectricField());
302
303 // Alias the field to send to the cell tagger
304 m_homogeneousFieldGas = m_amr->alias(phase::gas, m_electricFieldHomo);
305
306 if (m_debug) {
307
308 // Do a dummy check if the homogeneous and inhomogeneous solutions with a potential of one, and check that the
309 // difference is "sufficiently small"
310 const Real testVoltage = 1.0;
311 const Real errorThresh = 1.E-6;
312
313 // This is the sum of the two potentials
314 DataOps::setValue(m_potential, 0.0);
315 DataOps::incr(m_potential, m_potentialHomo, testVoltage);
316 DataOps::incr(m_potential, m_potentialInho, 1.0);
317
318 // Do a true solution with a test voltage and space/surface charge.
319 auto voltage = [V = testVoltage](const Real& a_time) {
320 return V;
321 };
322
323 DataOps::copy(m_fieldSolver->getPotential(), m_potential);
324
325 m_fieldSolver->setRho(m_rho);
326 m_fieldSolver->setSigma(m_sigma);
327 m_fieldSolver->setVoltage(voltage);
328 m_fieldSolver->solve(m_fieldSolver->getPotential(), m_fieldSolver->getRho(), m_fieldSolver->getSigma(), false);
329
330 // Do the difference between the two potentials and figure out the max error. It should be < 1.E-6 * testVoltage
331 DataOps::incr(m_potential, m_fieldSolver->getPotential(), -1.0);
332
333 EBAMRCellData phiGas = m_amr->alias(phase::gas, m_potential);
334
335 Real max;
336 Real min;
337
338 DataOps::getMaxMin(max, min, phiGas, 0, m_amr->getMultiCutVofIterator(m_realm, m_phase));
339
340 if (std::max(std::abs(max), std::abs(min)) > errorThresh * testVoltage) {
341 MayDay::Error("DischargeInceptionStepper::solvePoisson - debug test failed. Check your BCs!");
342 }
343 }
344}
345
346template <typename P, typename F, typename C>
347void
349{
350 CH_TIME("DischargeInceptionStepper::registerRealms");
351 if (m_verbosity > 5) {
352 pout() << "DischargeInceptionStepper::registerRealms" << endl;
353 }
354
355 m_amr->registerRealm(m_realm);
356}
357
358template <typename P, typename F, typename C>
359void
361{
362 CH_TIME("DischargeInceptionStepper::registerOperators");
363 if (m_verbosity > 5) {
364 pout() << "DischargeInceptionStepper::registerOperators" << endl;
365 }
366
367 m_fieldSolver->registerOperators();
368 m_tracerParticleSolver->registerOperators();
369 m_ionSolver->registerOperators();
370}
371
372template <typename P, typename F, typename C>
373void
375{
376 CH_TIME("DischargeInceptionStepper::parseOptions");
377
378 this->parseVerbosity();
379 this->parseMode();
380 this->parseVoltages();
381 this->parseOutput();
382 this->parsePlotVariables();
383 this->parseInceptionAlgorithm();
384 this->parseTransportAlgorithm();
385}
386
387template <typename P, typename F, typename C>
388void
390{
391 CH_TIME("DischargeInceptionStepper::parseRuntimeOptions");
392 if (m_verbosity > 5) {
393 pout() << "DischargeInceptionStepper::parseRuntimeOptions" << endl;
394 }
395
396 this->parseVerbosity();
397 this->parsePlotVariables();
398 this->parseInceptionAlgorithm();
399 this->parseTransportAlgorithm();
400}
401
402template <typename P, typename F, typename C>
403void
405{
406 CH_TIME("DischargeInceptionStepper::parseVerbosity");
407 if (m_verbosity > 5) {
408 pout() << "DischargeInceptionStepper::parseVerbosity" << endl;
409 }
410
411 ParmParse pp("DischargeInceptionStepper");
412 pp.get("verbosity", m_verbosity);
413 pp.get("profile", m_profile);
414 pp.query("debug", m_debug);
415}
416
417template <typename P, typename F, typename C>
418void
420{
421 CH_TIME("DischargeInceptionStepper::parseMode");
422 if (m_verbosity > 5) {
423 pout() << "DischargeInceptionStepper::parseMode" << endl;
424 }
425
426 ParmParse pp("DischargeInceptionStepper");
427
428 std::string str;
429
430 pp.get("mode", str);
431 if (str == "stationary") {
432 m_mode = Mode::Stationary;
433 }
434 else if (str == "transient") {
435 m_mode = Mode::Transient;
436 }
437 else {
438 MayDay::Error("Expected 'none', 'stationary', or 'transient' for 'DischargeInceptionStepper.mode'");
439 }
440}
441
442template <typename P, typename F, typename C>
443void
445{
446 CH_TIME("DischargeInceptionStepper::parseVoltages");
447 if (m_verbosity > 5) {
448 pout() << "DischargeInceptionStepper::parseVoltages" << endl;
449 }
450
451 ParmParse pp("DischargeInceptionStepper");
452
453 pp.get("rel_step_dU", m_relativeDeltaU);
454 pp.get("step_dK", m_deltaK);
455 pp.get("limit_max_K", m_maxKLimit);
456 pp.get("K_inception", m_inceptionK);
457 pp.get("eval_townsend", m_evaluateTownsend);
458
459 m_relativeDeltaU = 1.0 + m_relativeDeltaU;
460}
461
462template <typename P, typename F, typename C>
463void
465{
466 CH_TIME("DischargeInceptionStepper::parseOutput");
467 if (m_verbosity > 5) {
468 pout() << "DischargeInceptionStepper::parseOutput" << endl;
469 }
470
471 ParmParse pp("DischargeInceptionStepper");
472
473 pp.get("output_file", m_outputFile);
474}
475
476template <typename P, typename F, typename C>
477void
479{
480 CH_TIME("DischargeInceptionStepper::parseInceptionAlgorithm");
481 if (m_verbosity > 5) {
482 pout() << "DischargeInceptionStepper::parseInceptionAlgorithm" << endl;
483 }
484
485 ParmParse pp("DischargeInceptionStepper");
486
487 std::string str;
488
489 // Get the inception algorithm
490 pp.get("full_integration", m_fullIntegration);
491 pp.get("inception_alg", str);
492 if (str == "euler") {
493 m_inceptionAlgorithm = IntegrationAlgorithm::Euler;
494 }
495 else if (str == "trapz") {
496 m_inceptionAlgorithm = IntegrationAlgorithm::Trapezoidal;
497 }
498 else {
499 MayDay::Error("Expected 'euler' or 'trapz' for 'DischargeInceptionStepper.inception_alg'");
500 }
501
502 pp.get("min_phys_dx", m_minPhysDx);
503 pp.get("max_phys_dx", m_maxPhysDx);
504 pp.get("min_grid_dx", m_minGridDx);
505 pp.get("max_grid_dx", m_maxGridDx);
506 pp.get("alpha_dx", m_alphaDx);
507 pp.get("grad_alpha_dx", m_gradAlphaDx);
508 pp.get("townsend_grid_dx", m_townsendGridDx);
509
510 if (m_minPhysDx <= 0.0) {
511 MayDay::Abort("DischargeInceptionStepper.min_phys_dx must be > 0.0");
512 }
513 if (m_maxPhysDx <= 0.0) {
514 MayDay::Abort("DischargeInceptionStepper.max_phys_dx must be > 0.0");
515 }
516 if (m_minGridDx <= 0.0) {
517 MayDay::Abort("DischargeInceptionStepper.min_grid_dx must be > 0.0");
518 }
519 if (m_maxGridDx <= 0.0) {
520 MayDay::Abort("DischargeInceptionStepper.max_grid_dx must be > 0.0");
521 }
522 if (m_alphaDx <= 0.0) {
523 MayDay::Abort("DischargeInceptionStepper.alpha_dx must be > 0.0");
524 }
525 if (m_gradAlphaDx <= 0.0) {
526 MayDay::Abort("DischargeInceptionStepper.grad_alpha_dx must be > 0.0");
527 }
528 if (m_townsendGridDx <= 0.0) {
529 MayDay::Abort("DischargeInceptionStepper.townsend_grid_dx must be > 0.0");
530 }
531}
532
533template <typename P, typename F, typename C>
534void
536{
537 CH_TIME("DischargeInceptionStepper::parseTransportAlgorithm");
538 if (m_verbosity > 5) {
539 pout() << "DischargeInceptionStepper::parseTransportAlgorithm" << endl;
540 }
541
542 ParmParse pp("DischargeInceptionStepper");
543
544 std::string str;
545
546 pp.get("transport_alg", str);
547 pp.get("ion_transport", m_ionTransport);
548 pp.get("cfl", m_cfl);
549 pp.get("first_dt", m_firstDt);
550 pp.get("min_dt", m_minDt);
551 pp.get("max_dt", m_maxDt);
552 pp.get("max_dt_growth", m_maxDtGrowth);
553 pp.get("voltage_eps", m_epsVoltage);
554
555 CH_assert(m_minDt >= 0.0);
556 CH_assert(m_maxDt >= 0.0);
557 CH_assert(m_firstDt > 0.0);
558
559 if (str == "euler") {
560 m_transportAlgorithm = TransportAlgorithm::Euler;
561 }
562 else if (str == "heun") {
563 m_transportAlgorithm = TransportAlgorithm::Heun;
564 }
565 else if (str == "imex") {
566 m_transportAlgorithm = TransportAlgorithm::ImExCTU;
567 }
568 else {
569 MayDay::Error("Expected 'euler', 'heun', or 'imex' for 'DischargeInceptionStepper.transport_alg'");
570 }
571}
572
573template <typename P, typename F, typename C>
574void
576{
577 CH_TIME("DischargeInceptionStepper::parsePlotVariables");
578 if (m_verbosity > 5) {
579 pout() << "DischargeInceptionStepper::parsePlotVariables" << endl;
580 }
581
582 m_plotField = false;
583 m_plotPoisson = false;
584 m_plotTracer = false;
585 m_plotNegativeIons = false;
586 m_plotInceptionIntegral = false;
587 m_plotInceptionVoltage = false;
588 m_plotBackgroundIonization = false;
589 m_plotDetachment = false;
590 m_plotFieldEmission = false;
591 m_plotAlpha = false;
592 m_plotEta = false;
593 m_plotTownsend = false;
594
595 ParmParse pp("DischargeInceptionStepper");
596
597 // Get plot variables.
598 const int num = pp.countval("plt_vars");
599 if (num > 0) {
600 Vector<std::string> plotVars(num);
601 pp.getarr("plt_vars", plotVars, 0, num);
602
603 for (int i = 0; i < num; i++) {
604 if (plotVars[i] == "field") {
605 m_plotField = true;
606 }
607 else if (plotVars[i] == "poisson") {
608 m_plotPoisson = true;
609 }
610 else if (plotVars[i] == "tracer") {
611 m_plotTracer = true;
612 }
613 else if (plotVars[i] == "ions") {
614 m_plotNegativeIons = true;
615 }
616 else if (plotVars[i] == "K") {
617 m_plotInceptionIntegral = true;
618 }
619 else if (plotVars[i] == "Uinc") {
620 m_plotInceptionVoltage = true;
621 }
622 else if (plotVars[i] == "bg_rate") {
623 m_plotBackgroundIonization = true;
624 }
625 else if (plotVars[i] == "detachment") {
626 m_plotDetachment = true;
627 }
628 else if (plotVars[i] == "emission") {
629 m_plotFieldEmission = true;
630 }
631 else if (plotVars[i] == "alpha") {
632 m_plotAlpha = true;
633 }
634 else if (plotVars[i] == "eta") {
635 m_plotEta = true;
636 }
637 else if (plotVars[i] == "T") {
638 m_plotTownsend = true;
639 }
640 }
641 }
642}
643
644#ifdef CH_USE_HDF5
645template <typename P, typename F, typename C>
646void
647DischargeInceptionStepper<P, F, C>::writeCheckpointData(HDF5Handle& a_handle, const int a_lvl) const
648{
649 CH_TIME("DischargeInceptionStepper::writeCheckpointData");
650 if (m_verbosity > 5) {
651 pout() << "DischargeInceptionStepper::writeCheckpointData" << endl;
652 }
653}
654#endif
655
656#ifdef CH_USE_HDF5
657template <typename P, typename F, typename C>
658void
659DischargeInceptionStepper<P, F, C>::readCheckpointData(HDF5Handle& a_handle, const int a_lvl)
660{
661 CH_TIME("DischargeInceptionStepper::readCheckpointData");
662 if (m_verbosity > 5) {
663 pout() << "DischargeInceptionStepper::readCheckpointData" << endl;
664 }
665
666 MayDay::Error("DischargeInceptionStepper::readCheckpointData -- restart not supported. Use Driver.restart=0");
667}
668#endif
669
670template <typename P, typename F, typename C>
671int
673{
674 CH_TIME("DischargeInceptionStepper::getNumberOfPlotVariables");
675 if (m_verbosity > 5) {
676 pout() << "DischargeInceptionStepper::getNumberOfPlotVariables" << endl;
677 }
678
679 int ncomp = 0;
680
681 if (m_plotPoisson) {
682 ncomp += m_fieldSolver->getNumberOfPlotVariables();
683 }
684 if (m_plotTracer) {
685 ncomp += m_tracerParticleSolver->getNumberOfPlotVariables();
686 }
687 if (m_plotNegativeIons) {
688 ncomp += m_ionSolver->getNumberOfPlotVariables();
689 }
690
691 switch (m_mode) {
692 case Mode::Stationary: {
693
694 if (m_plotField) {
695 // Electric potential for each voltage
696 ncomp += 2 * m_voltageSweeps.size();
697
698 // Electric field magnitude
699 ncomp += 2 * m_voltageSweeps.size();
700
701 // All the electric field components
702 ncomp += 2 * SpaceDim * m_voltageSweeps.size();
703
704 // Surface and space charge densities
705 ncomp += 2;
706 }
707
708 // K-values
709 if (m_plotInceptionIntegral) {
710 ncomp += 2 * m_voltageSweeps.size();
711 }
712
713 // Inception voltage.
714 if (m_plotInceptionVoltage) {
715 ncomp += 6;
716 }
717
718 // Background ionization rates
719 if (m_plotBackgroundIonization) {
720 ncomp += m_voltageSweeps.size();
721 }
722
723 // Detachment rates
724 if (m_plotDetachment) {
725 ncomp += m_voltageSweeps.size();
726 }
727
728 // Field emission rates
729 if (m_plotFieldEmission) {
730 ncomp += 2 * m_voltageSweeps.size();
731 }
732
733 // Plotting alpha
734 if (m_plotAlpha) {
735 ncomp += m_voltageSweeps.size();
736 }
737
738 // Plotting eta
739 if (m_plotEta) {
740 ncomp += m_voltageSweeps.size();
741 }
742
743 // When plotting both, add the effective ionization coefficient
744 if (m_plotAlpha && m_plotEta) {
745 ncomp += m_voltageSweeps.size();
746 }
747
748 // Plotting gamma coefficient
749 if (m_plotTownsend) {
750 ncomp += 2 * m_voltageSweeps.size();
751 }
752
753 break;
754 }
755 case Mode::Transient: {
756
757 if (m_plotField) {
758
759 // Potential
760 ncomp += 1;
761
762 // Field magnitude
763 ncomp += 1;
764
765 // Field components
766 ncomp += SpaceDim;
767
768 // Space and surface charge
769 ncomp += 2;
770 }
771
772 if (m_plotInceptionIntegral) {
773 ncomp += 1;
774 }
775 if (m_plotTownsend) {
776 ncomp += 1;
777 }
778 if (m_plotBackgroundIonization) {
779 ncomp += 1;
780 }
781 if (m_plotDetachment) {
782 ncomp += 1;
783 }
784 if (m_plotFieldEmission) {
785 ncomp += 1;
786 }
787 if (m_plotAlpha) {
788 ncomp += 1;
789 }
790 if (m_plotEta) {
791 ncomp += 1;
792 }
793 if (m_plotAlpha && m_plotEta) {
794 ncomp += 1;
795 }
796
797 break;
798 }
799 default: {
800 break;
801 }
802 }
803
804 return ncomp;
805}
806
807template <typename P, typename F, typename C>
808Vector<std::string>
810{
811 CH_TIME("DischargeInceptionStepper::getPlotVariableNames");
812 if (m_verbosity > 5) {
813 pout() << "DischargeInceptionStepper::getPlotVariableNames" << endl;
814 }
815
816 Vector<std::string> plotVars;
817
818 if (m_plotPoisson) {
819 Vector<std::string> poissonVars = m_fieldSolver->getPlotVariableNames();
820 for (int i = 0; i < poissonVars.size(); i++) {
821 poissonVars[i] = "Poisson/" + poissonVars[i];
822 }
823 plotVars.append(poissonVars);
824 }
825
826 if (m_plotTracer) {
827 Vector<std::string> tracerVars = m_tracerParticleSolver->getPlotVariableNames();
828 for (int i = 0; i < tracerVars.size(); i++) {
829 tracerVars[i] = "Tracer/" + tracerVars[i];
830 }
831 plotVars.append(tracerVars);
832 }
833
834 if (m_plotNegativeIons) {
835 Vector<std::string> cdrVars = m_ionSolver->getPlotVariableNames();
836 for (int i = 0; i < cdrVars.size(); i++) {
837 cdrVars[i] = "CDR/" + cdrVars[i];
838 }
839 plotVars.append(cdrVars);
840 }
841
842 switch (m_mode) {
843 case Mode::Stationary: {
844 plotVars.append(this->getStationaryPlotVariableNames());
845
846 break;
847 }
848 case Mode::Transient: {
849 plotVars.append(this->getTransientPlotVariableNames());
850
851 break;
852 }
853 default: {
854 MayDay::Error("DischargeInceptionStepper::getPlotVariableNames - logic bust");
855
856 break;
857 }
858 }
859
860 return plotVars;
861}
862
863template <typename P, typename F, typename C>
864Vector<std::string>
866{
867 CH_TIME("DischargeInceptionStepper::getStationaryPlotVariableNames");
868 if (m_verbosity > 5) {
869 pout() << "DischargeInceptionStepper::getStationaryPlotVariableNames" << endl;
870 }
871
872 Vector<std::string> plotVars;
873
874 const std::string prefix = "DischargeInceptionStepper/";
875
876 // Always plotted.
877 if (m_plotField) {
878 for (const Real& V : m_voltageSweeps) {
879 plotVars.push_back(prefix + "Potential/+/ V = +" + std::to_string(V));
880 plotVars.push_back(prefix + "Potential/-/ V = -" + std::to_string(V));
881 }
882
883 for (const Real& V : m_voltageSweeps) {
884 plotVars.push_back(prefix + "E/+/ V = +" + std::to_string(V));
885 plotVars.push_back(prefix + "x-E/+/ V= +" + std::to_string(V));
886 plotVars.push_back(prefix + "y-E/+/ V= +" + std::to_string(V));
887 if (SpaceDim == 3) {
888 plotVars.push_back(prefix + "z-E/+/ V= +" + std::to_string(V));
889 }
890
891 plotVars.push_back(prefix + "E/-/ V = -" + std::to_string(V));
892 plotVars.push_back(prefix + "x-E/-/ V = -" + std::to_string(V));
893 plotVars.push_back(prefix + "y-E/-/ V = -" + std::to_string(V));
894 if (SpaceDim == 3) {
895 plotVars.push_back(prefix + "z-E/-/ V= -" + std::to_string(V));
896 }
897 }
898
899 // Surface charge
900 plotVars.push_back(prefix + "Space charge density");
901 plotVars.push_back(prefix + "Surface charge density");
902 }
903
904 if (m_plotInceptionVoltage) {
905 plotVars.push_back(prefix + "Minimum inception voltage +");
906 plotVars.push_back(prefix + "Minimum inception voltage -");
907 plotVars.push_back(prefix + "Streamer inception voltage +");
908 plotVars.push_back(prefix + "Streamer inception voltage -");
909 plotVars.push_back(prefix + "Townsend inception voltage +");
910 plotVars.push_back(prefix + "Townsend inception voltage -");
911 }
912
913 if (m_plotInceptionIntegral) {
914 std::string varName;
915
916 for (const Real& V : m_voltageSweeps) {
917 varName = prefix + "K-value/+/ V = +" + std::to_string(V);
918 plotVars.push_back(varName);
919 }
920
921 for (const Real& V : m_voltageSweeps) {
922 varName = prefix + "K-value/-/ V = -" + std::to_string(V);
923 plotVars.push_back(varName);
924 }
925 }
926
927 // Secondary emission coefficient for initiatory ions
928 if (m_plotTownsend) {
929 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
930 const std::string varName = prefix + "T-value/+/ V = +" + std::to_string(m_voltageSweeps[i]);
931
932 plotVars.push_back(varName);
933 }
934
935 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
936 const std::string varName = prefix + "T-value/-/ V = -" + std::to_string(m_voltageSweeps[i]);
937
938 plotVars.push_back(varName);
939 }
940 }
941
942 // Write the background ionization rates
943 if (m_plotBackgroundIonization) {
944 std::string varName;
945
946 for (const Real& V : m_voltageSweeps) {
947 varName = prefix + "Background ionization rate/ V = " + std::to_string(V);
948 plotVars.push_back(varName);
949 }
950 }
951
952 // Write detachment rates
953 if (m_plotDetachment) {
954 std::string varName;
955
956 for (const Real& V : m_voltageSweeps) {
957 varName = prefix + "Detachment rate/ V = " + std::to_string(V);
958 plotVars.push_back(varName);
959 }
960 }
961
962 // Write field emission rates
963 if (m_plotFieldEmission) {
964 std::string varName;
965
966 for (const Real& V : m_voltageSweeps) {
967 varName = prefix + "Field emission rate/+/ V = +" + std::to_string(V);
968 plotVars.push_back(varName);
969 }
970
971 for (const Real& V : m_voltageSweeps) {
972 varName = prefix + "Field emission rate/-/ V = -" + std::to_string(V);
973 plotVars.push_back(varName);
974 }
975 }
976
977 // Alpha value
978 if (m_plotAlpha) {
979 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
980
981 const std::string varName = prefix + "Alpha coefficient/ V = " + std::to_string(m_voltageSweeps[i]);
982
983 plotVars.push_back(varName);
984 }
985 }
986
987 // Eta
988 if (m_plotEta) {
989 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
990 const std::string varName = prefix + "Eta coefficient/ V = " + std::to_string(m_voltageSweeps[i]);
991
992 plotVars.push_back(varName);
993 }
994 }
995
996 // Effective alpha coefficient
997 if (m_plotAlpha && m_plotEta) {
998 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
999 const std::string varName = prefix + "Alpha_eff/ V = " + std::to_string(m_voltageSweeps[i]);
1000
1001 plotVars.push_back(varName);
1002 }
1003 }
1004
1005 return plotVars;
1006}
1007
1008template <typename P, typename F, typename C>
1009Vector<std::string>
1011{
1012 CH_TIME("DischargeInceptionStepper::getTransientPlotVariableNames");
1013 if (m_verbosity > 5) {
1014 pout() << "DischargeInceptionStepper::getTransientPlotVariableNames" << endl;
1015 }
1016
1017 Vector<std::string> plotVars;
1018
1019 if (m_plotField) {
1020 // Add potential and electric field to output
1021 plotVars.push_back("Electric potential");
1022 plotVars.push_back("E");
1023 plotVars.push_back("x-E");
1024 plotVars.push_back("y-E");
1025 if (SpaceDim == 3) {
1026 plotVars.push_back("z-E");
1027 }
1028
1029 // Space and surface charge
1030 plotVars.push_back("Space charge density");
1031 plotVars.push_back("Surface charge density");
1032 }
1033
1034 if (m_plotInceptionIntegral) {
1035 plotVars.push_back("Inception integral");
1036 }
1037
1038 if (m_plotTownsend) {
1039 plotVars.push_back("Townsend criterion");
1040 }
1041
1042 if (m_plotBackgroundIonization) {
1043 plotVars.push_back("Background rate");
1044 }
1045
1046 if (m_plotDetachment) {
1047 plotVars.push_back("Detachment rate");
1048 }
1049
1050 if (m_plotFieldEmission) {
1051 plotVars.push_back("Field emission");
1052 }
1053
1054 if (m_plotAlpha) {
1055 plotVars.push_back("Townsend alpha coefficient");
1056 }
1057
1058 if (m_plotEta) {
1059 plotVars.push_back("Townsend eta coefficient");
1060 }
1061
1062 if (m_plotAlpha && m_plotEta) {
1063 plotVars.push_back("Effective Townsend coefficient");
1064 }
1065
1066 for (int i = 0; i < plotVars.size(); i++) {
1067 plotVars[i] = "DischargeInceptionStepper/" + plotVars[i];
1068 }
1069
1070 return plotVars;
1071}
1072
1073template <typename P, typename F, typename C>
1074void
1076 int& a_icomp,
1077 const std::string& a_outputRealm,
1078 const int a_level) const
1079{
1080 CH_TIME("DischargeInceptionStepper::writePlotData");
1081 if (m_verbosity > 5) {
1082 pout() << "DischargeInceptionStepper::writePlotData" << endl;
1083 }
1084
1085 if (m_plotPoisson) {
1086 m_fieldSolver->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1087 }
1088
1089 if (m_plotTracer) {
1090 m_tracerParticleSolver->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1091 }
1092
1093 if (m_plotNegativeIons) {
1094 m_ionSolver->writePlotData(a_output, a_icomp, a_outputRealm, a_level);
1095 }
1096
1097 // Write internal data -- this differs between the 'stationary' and 'transient' modes
1098 switch (m_mode) {
1099 case Mode::Stationary: {
1100 this->writePlotDataStationary(a_output, a_icomp, a_outputRealm, a_level);
1101
1102 break;
1103 }
1104 case Mode::Transient:
1105 this->writePlotDataTransient(a_output, a_icomp, a_outputRealm, a_level);
1106
1107 break;
1108 default: {
1109 MayDay::Error("DischargeInceptionStepper::writePlotData - logic bust");
1110
1111 break;
1112 }
1113 }
1114}
1115
1116template <typename P, typename F, typename C>
1117void
1119 int& a_icomp,
1120 const std::string& a_outputRealm,
1121 const int a_level) const noexcept
1122{
1123 CH_TIME("DischargeInceptionStepper::writePlotDataStationary");
1124 if (m_verbosity > 5) {
1125 pout() << "DischargeInceptionStepper::writePlotDataStationary" << endl;
1126 }
1127
1128 const std::string prefix = "DischargeInceptionStepper/";
1129
1130 if (m_plotField) {
1131 MFAMRCellData scratch;
1132 EBAMRIVData scratchSurf;
1133
1134 m_amr->allocate(scratch, m_realm, 1);
1135 m_amr->allocate(scratchSurf, m_realm, phase::gas, 1);
1136
1137 for (const auto& V : m_voltageSweeps) {
1138
1139 // Potential for positive applied voltage
1140 DataOps::setValue(m_potential, 0.0);
1141 DataOps::incr(m_potential, m_potentialHomo, Real(V));
1142 DataOps::incr(m_potential, m_potentialInho, 1.0);
1143 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_potential, phase::gas, a_outputRealm, a_level, true);
1144
1145 // Potential for negative applied voltage
1146 DataOps::setValue(m_potential, 0.0);
1147 DataOps::incr(m_potential, m_potentialHomo, -Real(V));
1148 DataOps::incr(m_potential, m_potentialInho, 1.0);
1149 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_potential, phase::gas, a_outputRealm, a_level, true);
1150 }
1151
1152 for (const auto& V : m_voltageSweeps) {
1153 // Field magnitude for positive applied field
1154 DataOps::setValue(m_electricField, 0.0);
1155 DataOps::incr(m_electricField, m_electricFieldHomo, Real(V));
1156 DataOps::incr(m_electricField, m_electricFieldInho, 1.0);
1157 DataOps::dotProduct(scratch, m_electricField, m_electricField);
1158 DataOps::squareRoot(scratch,
1159 m_amr->getMultiCutVofIterator(m_realm, phase::gas),
1160 m_amr->getMultiCutVofIterator(m_realm, phase::solid));
1161 m_fieldSolver->writeMultifluidData(a_output, a_icomp, scratch, phase::gas, a_outputRealm, a_level, true);
1162 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_electricField, phase::gas, a_outputRealm, a_level, true);
1163
1164 // Field magnitude for positive applied field
1165 DataOps::setValue(m_electricField, 0.0);
1166 DataOps::incr(m_electricField, m_electricFieldHomo, -Real(V));
1167 DataOps::incr(m_electricField, m_electricFieldInho, 1.0);
1168 DataOps::dotProduct(scratch, m_electricField, m_electricField);
1169 DataOps::squareRoot(scratch,
1170 m_amr->getMultiCutVofIterator(m_realm, phase::gas),
1171 m_amr->getMultiCutVofIterator(m_realm, phase::solid));
1172 m_fieldSolver->writeMultifluidData(a_output, a_icomp, scratch, phase::gas, a_outputRealm, a_level, true);
1173 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_electricField, phase::gas, a_outputRealm, a_level, true);
1174 }
1175
1176 // Write the space charge density
1177 DataOps::setValue(scratch,
1178 m_rho,
1179 m_amr->getProbLo(),
1180 m_amr->getDx(),
1181 0,
1182 m_amr->getVofIterator(m_realm, phase::gas),
1183 m_amr->getVofIterator(m_realm, phase::solid));
1184 m_amr->arithmeticAverage(scratch, m_realm);
1185 m_amr->interpGhostPwl(scratch, m_realm);
1186 m_fieldSolver->writeMultifluidData(a_output, a_icomp, scratch, phase::gas, a_outputRealm, a_level, true);
1187
1188 DataOps::setValue(scratchSurf,
1189 m_sigma,
1190 m_amr->getProbLo(),
1191 m_amr->getDx(),
1192 0,
1193 m_amr->getVofIterator(m_realm, phase::gas));
1194 m_fieldSolver->writeSurfaceData(a_output, a_icomp, *scratchSurf[a_level], a_outputRealm, a_level);
1195 }
1196
1197 if (m_plotInceptionVoltage) {
1198 this->writeData(a_output, a_icomp, m_inceptionVoltagePlus, a_outputRealm, a_level, false, true);
1199 this->writeData(a_output, a_icomp, m_inceptionVoltageMinu, a_outputRealm, a_level, false, true);
1200 this->writeData(a_output, a_icomp, m_streamerInceptionVoltagePlus, a_outputRealm, a_level, false, true);
1201 this->writeData(a_output, a_icomp, m_streamerInceptionVoltageMinu, a_outputRealm, a_level, false, true);
1202 this->writeData(a_output, a_icomp, m_townsendInceptionVoltagePlus, a_outputRealm, a_level, false, true);
1203 this->writeData(a_output, a_icomp, m_townsendInceptionVoltageMinu, a_outputRealm, a_level, false, true);
1204 }
1205
1206 const int numVoltages = m_voltageSweeps.size();
1207
1208 if (m_plotInceptionIntegral) {
1209 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1210 this->writeData(a_output, a_icomp, m_inceptionIntegralPlus[i], a_outputRealm, a_level, false, true);
1211 }
1212 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1213 this->writeData(a_output, a_icomp, m_inceptionIntegralMinu[i], a_outputRealm, a_level, false, true);
1214 }
1215 }
1216
1217 if (m_plotTownsend) {
1218 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1219 this->writeData(a_output, a_icomp, m_townsendCriterionPlus[i], a_outputRealm, a_level, false, true);
1220 }
1221
1222 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1223 this->writeData(a_output, a_icomp, m_townsendCriterionMinu[i], a_outputRealm, a_level, false, true);
1224 }
1225 }
1226
1227 if (m_plotBackgroundIonization) {
1228 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1229 this->writeData(a_output, a_icomp, m_backgroundIonizationStationary[i], a_outputRealm, a_level, false, true);
1230 }
1231 }
1232
1233 if (m_plotDetachment) {
1234 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1235 this->writeData(a_output, a_icomp, m_detachmentStationary[i], a_outputRealm, a_level, false, true);
1236 }
1237 }
1238
1239 if (m_plotFieldEmission) {
1240 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1241 this->writeData(a_output, a_icomp, m_emissionRatesPlus[i], a_outputRealm, a_level, false, true);
1242 }
1243 for (int i = 0; i < m_voltageSweeps.size(); i++) {
1244 this->writeData(a_output, a_icomp, m_emissionRatesMinu[i], a_outputRealm, a_level, false, true);
1245 }
1246 }
1247
1248 if (m_plotAlpha) {
1249 LevelData<EBCellFAB> alpha;
1250 LevelData<EBCellFAB> alphaCoar;
1251
1252 m_amr->allocate(alpha, m_realm, m_phase, a_level, 1);
1253 if (a_level > 0) {
1254 m_amr->allocate(alphaCoar, m_realm, m_phase, a_level - 1, 1);
1255 }
1256
1257 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
1258 this->evaluateFunction(alpha, m_voltageSweeps[i], m_alpha, a_level);
1259 if (a_level > 0) {
1260 this->evaluateFunction(alphaCoar, m_voltageSweeps[i], m_alpha, a_level - 1);
1261 m_amr->interpGhost(alpha, alphaCoar, a_level, m_realm, m_phase);
1262 }
1263 else {
1264 alpha.exchange();
1265 }
1266
1267 m_amr->copyData(a_output, alpha, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1268
1269 a_icomp++;
1270 }
1271 }
1272
1273 if (m_plotEta) {
1274 LevelData<EBCellFAB> eta;
1275 LevelData<EBCellFAB> etaCoar;
1276
1277 m_amr->allocate(eta, m_realm, m_phase, a_level, 1);
1278 if (a_level > 0) {
1279 m_amr->allocate(etaCoar, m_realm, m_phase, a_level - 1, 1);
1280 }
1281
1282 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
1283 this->evaluateFunction(eta, m_voltageSweeps[i], m_eta, a_level);
1284 if (a_level > 0) {
1285 this->evaluateFunction(etaCoar, m_voltageSweeps[i], m_eta, a_level - 1);
1286 m_amr->interpGhost(eta, etaCoar, a_level, m_realm, m_phase);
1287 }
1288 else {
1289 eta.exchange();
1290 }
1291
1292 m_amr->copyData(a_output, eta, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1293
1294 a_icomp++;
1295 }
1296 }
1297
1298 if (m_plotAlpha && m_plotEta) {
1299 LevelData<EBCellFAB> alphaEff;
1300 LevelData<EBCellFAB> alphaEffCoar;
1301
1302 m_amr->allocate(alphaEff, m_realm, m_phase, a_level, 1);
1303 if (a_level > 0) {
1304 m_amr->allocate(alphaEffCoar, m_realm, m_phase, a_level - 1, 1);
1305 }
1306
1307 auto alphaFunc = [alpha = this->m_alpha, eta = this->m_eta](const Real E, const RealVect x) -> Real {
1308 return alpha(E, x) - eta(E, x);
1309 };
1310
1311 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
1312 this->evaluateFunction(alphaEff, m_voltageSweeps[i], alphaFunc, a_level);
1313 if (a_level > 0) {
1314 this->evaluateFunction(alphaEffCoar, m_voltageSweeps[i], alphaFunc, a_level - 1);
1315 m_amr->interpGhost(alphaEff, alphaEffCoar, a_level, m_realm, m_phase);
1316 }
1317 else {
1318 alphaEff.exchange();
1319 }
1320
1321 // Do a copy, but note that this does not include ghost cells across the CF interface since
1322 // we are only computing for a single grid level.
1323 m_amr->copyData(a_output, alphaEff, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1324
1325 a_icomp++;
1326 }
1327 }
1328}
1329
1330template <typename P, typename F, typename C>
1331void
1333 int& a_icomp,
1334 const std::string& a_outputRealm,
1335 const int a_level) const noexcept
1336{
1337 CH_TIME("DischargeInceptionStepper::writePlotDataTransient");
1338 if (m_verbosity > 5) {
1339 pout() << "DischargeInceptionStepper::writePlotDataTransient" << endl;
1340 }
1341
1342 CH_assert(a_level >= 0);
1343 CH_assert(a_level <= m_amr->getFinestLevel());
1344
1345 // Add potential and electric field to output. Note that we need to scale appropriately.
1346 if (m_plotField) {
1347
1348 // Holds the field magnitude
1349 MFAMRCellData scratch;
1350 EBAMRIVData scratchSurf;
1351
1352 m_amr->allocate(scratch, m_realm, 1);
1353 m_amr->allocate(scratchSurf, m_realm, phase::gas, 1);
1354
1355 DataOps::dotProduct(scratch, m_electricField, m_electricField);
1356 DataOps::squareRoot(scratch,
1357 m_amr->getMultiCutVofIterator(m_realm, phase::gas),
1358 m_amr->getMultiCutVofIterator(m_realm, phase::solid));
1359 m_amr->arithmeticAverage(scratch, m_realm);
1360 m_amr->interpGhostPwl(scratch, m_realm);
1361
1362 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_potential, phase::gas, a_outputRealm, a_level, true);
1363 m_fieldSolver->writeMultifluidData(a_output, a_icomp, scratch, phase::gas, a_outputRealm, a_level, true);
1364 m_fieldSolver->writeMultifluidData(a_output, a_icomp, m_electricField, phase::gas, a_outputRealm, a_level, true);
1365
1366 // Write the space charge density
1367 DataOps::setValue(scratch,
1368 m_rho,
1369 m_amr->getProbLo(),
1370 m_amr->getDx(),
1371 0,
1372 m_amr->getVofIterator(m_realm, phase::gas),
1373 m_amr->getVofIterator(m_realm, phase::solid));
1374 m_amr->arithmeticAverage(scratch, m_realm);
1375 m_amr->interpGhostPwl(scratch, m_realm);
1376 m_fieldSolver->writeMultifluidData(a_output, a_icomp, scratch, phase::gas, a_outputRealm, a_level, true);
1377
1378 DataOps::setValue(scratchSurf,
1379 m_sigma,
1380 m_amr->getProbLo(),
1381 m_amr->getDx(),
1382 0,
1383 m_amr->getVofIterator(m_realm, phase::gas));
1384 m_fieldSolver->writeSurfaceData(a_output, a_icomp, *scratchSurf[a_level], a_outputRealm, a_level);
1385 }
1386
1387 if (m_plotInceptionIntegral) {
1388 this->writeData(a_output, a_icomp, m_inceptionIntegral, a_outputRealm, a_level, false, true);
1389 }
1390
1391 if (m_plotTownsend) {
1392 this->writeData(a_output, a_icomp, m_townsendCriterion, a_outputRealm, a_level, false, true);
1393 }
1394
1395 if (m_plotBackgroundIonization) {
1396 LevelData<EBCellFAB> bgIonization;
1397 m_amr->allocate(bgIonization, m_realm, m_phase, a_level, 1);
1398
1399 this->evaluateFunction(bgIonization, m_voltageCurve(m_time), m_backgroundRate, a_level);
1400
1401 // Do a copy, but note that this does not include ghost cells across the CF interface since
1402 // we are only computing for a single grid level.
1403 m_amr
1404 ->copyData(a_output, bgIonization, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1405
1406 a_icomp++;
1407 }
1408
1409 if (m_plotDetachment) {
1410 LevelData<EBCellFAB> detachRate;
1411 LevelData<EBCellFAB> detachRateCoar;
1412
1413 m_amr->allocate(detachRate, m_realm, m_phase, a_level, 1);
1414 if (a_level > 0) {
1415 m_amr->allocate(detachRateCoar, m_realm, m_phase, a_level - 1, 1);
1416 }
1417
1418 this->evaluateFunction(detachRate, m_voltageCurve(m_time), m_detachmentRate, a_level);
1419 if (a_level > 0) {
1420 this->evaluateFunction(detachRateCoar, m_voltageCurve(m_time), m_detachmentRate, a_level - 1);
1421 m_amr->interpGhost(detachRate, detachRateCoar, a_level, m_realm, m_phase);
1422 }
1423
1424 m_amr->copyData(a_output, detachRate, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1425
1426 a_icomp++;
1427 }
1428
1429 if (m_plotFieldEmission) {
1430 this->writeData(a_output, a_icomp, m_emissionRate, a_outputRealm, a_level, false, false);
1431 }
1432
1433 if (m_plotAlpha) {
1434 LevelData<EBCellFAB> alpha;
1435 LevelData<EBCellFAB> alphaCoar;
1436
1437 m_amr->allocate(alpha, m_realm, m_phase, a_level, 1);
1438 if (a_level > 0) {
1439 m_amr->allocate(alphaCoar, m_realm, m_phase, a_level - 1, 1);
1440 }
1441
1442 this->evaluateFunction(alpha, m_voltageCurve(m_time), m_alpha, a_level);
1443 if (a_level > 0) {
1444 this->evaluateFunction(alphaCoar, m_voltageCurve(m_time), m_alpha, a_level - 1);
1445 m_amr->interpGhost(alpha, alphaCoar, a_level, m_realm, m_phase);
1446 }
1447 else {
1448 alpha.exchange();
1449 }
1450
1451 // Do a copy, but note that this does not include ghost cells across the CF interface since
1452 // we are only computing for a single grid level.
1453 m_amr->copyData(a_output, alpha, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1454
1455 a_icomp++;
1456 }
1457
1458 if (m_plotEta) {
1459 LevelData<EBCellFAB> eta;
1460 LevelData<EBCellFAB> etaCoar;
1461
1462 m_amr->allocate(eta, m_realm, m_phase, a_level, 1);
1463 if (a_level > 0) {
1464 m_amr->allocate(etaCoar, m_realm, m_phase, a_level - 1, 1);
1465 }
1466
1467 this->evaluateFunction(eta, m_voltageCurve(m_time), m_eta, a_level);
1468 if (a_level > 0) {
1469 this->evaluateFunction(etaCoar, m_voltageCurve(m_time), m_eta, a_level - 1);
1470 m_amr->interpGhost(eta, etaCoar, a_level, m_realm, m_phase);
1471 }
1472 else {
1473 eta.exchange();
1474 }
1475
1476 // Do a copy, but note that this does not include ghost cells across the CF interface since
1477 // we are only computing for a single grid level.
1478 m_amr->copyData(a_output, eta, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1479
1480 a_icomp++;
1481 }
1482
1483 if (m_plotAlpha && m_plotEta) {
1484 LevelData<EBCellFAB> alphaEff;
1485 LevelData<EBCellFAB> alphaEffCoar;
1486
1487 m_amr->allocate(alphaEff, m_realm, m_phase, a_level, 1);
1488 if (a_level > 0) {
1489 m_amr->allocate(alphaEffCoar, m_realm, m_phase, a_level - 1, 1);
1490 }
1491
1492 auto alphaFunc = [alpha = this->m_alpha, eta = this->m_eta](const Real E, const RealVect x) -> Real {
1493 return alpha(E, x) - eta(E, x);
1494 };
1495
1496 this->evaluateFunction(alphaEff, m_voltageCurve(m_time), alphaFunc, a_level);
1497 if (a_level > 0) {
1498 this->evaluateFunction(alphaEffCoar, m_voltageCurve(m_time), alphaFunc, a_level - 1);
1499 m_amr->interpGhost(alphaEff, alphaEffCoar, a_level, m_realm, m_phase);
1500 }
1501 else {
1502 alphaEff.exchange();
1503 }
1504
1505 // Do a copy, but note that this does not include ghost cells across the CF interface since
1506 // we are only computing for a single grid level.
1507 m_amr->copyData(a_output, alphaEff, a_level, a_outputRealm, m_realm, Interval(a_icomp, a_icomp), Interval(0, 0));
1508
1509 a_icomp++;
1510 }
1511}
1512
1513template <typename P, typename F, typename C>
1514Real
1516{
1517 CH_TIME("DischargeInceptionStepper::computeDt");
1518 if (m_verbosity > 5) {
1519 pout() << "DischargeInceptionStepper::computeDt" << endl;
1520 }
1521
1522 Real dt = std::numeric_limits<Real>::max();
1523
1524 if (m_mode == Mode::Transient) {
1525
1526 // Compute time step from ion solver.
1527 m_timeStepRestriction = TimeStepRestriction::Unknown;
1528
1529 // Restrict by ion solver
1530 if (m_ionTransport) {
1531 Real ionDt = std::numeric_limits<Real>::infinity();
1532 switch (m_transportAlgorithm) {
1533 case TransportAlgorithm::Euler: {
1534 ionDt = m_cfl * m_ionSolver->computeAdvectionDiffusionDt();
1535
1536 break;
1537 }
1538 case TransportAlgorithm::Heun: {
1539 ionDt = m_cfl * m_ionSolver->computeAdvectionDiffusionDt();
1540
1541 break;
1542 }
1543 case TransportAlgorithm::ImExCTU: {
1544 ionDt = m_cfl * m_ionSolver->computeAdvectionDt();
1545
1546 break;
1547 }
1548 default: {
1549 MayDay::Error("DischargeInceptionStepper::computDt -- logic bust");
1550
1551 break;
1552 }
1553 }
1554
1555 if (ionDt < dt) {
1556 dt = ionDt;
1557 m_timeStepRestriction = TimeStepRestriction::CDR;
1558 }
1559 }
1560
1561 // Restrict by voltage curve.
1562 Real curveDt = std::numeric_limits<Real>::max();
1563 if (m_timeStep == 0) {
1564 curveDt = m_firstDt;
1565 }
1566 else {
1567 const Real preU = m_voltageCurve(m_time - m_dt);
1568 const Real curU = m_voltageCurve(m_time);
1569 const Real dVdt = std::abs(curU - preU) / dt;
1570
1571 if (dVdt > std::numeric_limits<Real>::epsilon()) {
1572 curveDt = m_epsVoltage * std::abs(curU) / dVdt;
1573 }
1574
1575 // Do not grow too fast.
1576 curveDt = std::min(m_dt * (1.0 + m_maxDtGrowth), curveDt);
1577 curveDt = std::max(m_dt * (1.0 - m_maxDtGrowth), curveDt);
1578 }
1579
1580 if (curveDt < dt) {
1581 dt = curveDt;
1582 m_timeStepRestriction = TimeStepRestriction::VoltageCurve;
1583 }
1584
1585 // Restrict by hardcaps
1586 if (dt > m_maxDt) {
1587 dt = m_maxDt;
1588 m_timeStepRestriction = TimeStepRestriction::MaxHardcap;
1589 }
1590
1591 if (dt < m_minDt) {
1592 dt = m_minDt;
1593 m_timeStepRestriction = TimeStepRestriction::MinHardcap;
1594 }
1595 }
1596
1597 return dt;
1598}
1599
1600template <typename P, typename F, typename C>
1601Real
1603{
1604 CH_TIME("DischargeInceptionStepper::advance");
1605 if (m_verbosity > 5) {
1606 pout() << "DischargeInceptionStepper::advance" << endl;
1607 }
1608
1609 if (m_mode == Mode::Transient) {
1610 Timer timer("DischargeInceptionStepper::advance");
1611
1612 const Real curTime = m_time + a_dt;
1613 const Real curVoltage = m_voltageCurve(curTime);
1614
1615 // Compute the electric potential and field at the current time step -- these are used in the output routines
1616 DataOps::setValue(m_potential, 0.0);
1617 DataOps::incr(m_potential, m_potentialHomo, curVoltage);
1618 DataOps::incr(m_potential, m_potentialInho, 1.0);
1619
1620 m_amr->arithmeticAverage(m_potential, m_realm);
1621 m_amr->interpGhostPwl(m_potential, m_realm);
1622
1623 DataOps::setValue(m_electricField, 0.0);
1624 DataOps::incr(m_electricField, m_electricFieldHomo, curVoltage);
1625 DataOps::incr(m_electricField, m_electricFieldInho, 1.0);
1626
1627 m_amr->arithmeticAverage(m_electricField, m_realm);
1628 m_amr->interpGhostPwl(m_electricField, m_realm);
1629
1630 // Move the negative ions -- still want this for the time step.
1631 timer.startEvent("Ion advance");
1632 if (m_ionTransport) {
1633 this->computeIonVelocity(curVoltage);
1634 this->computeIonDiffusion(curVoltage);
1635 this->advanceIons(a_dt);
1636 }
1637 timer.stopEvent("Ion advance");
1638
1639 // Seed new particles and compute the inception integral.
1640 timer.startEvent("Inception integral");
1641 this->seedIonizationParticles(curVoltage);
1642 this->computeInceptionIntegralTransient(curVoltage);
1643 timer.stopEvent("Inception integral");
1644
1645 if (m_evaluateTownsend) {
1646 timer.startEvent("Townsend criterion");
1647 this->seedIonizationParticles(curVoltage);
1648 this->computeTownsendCriterionTransient(curVoltage);
1649 timer.stopEvent("Townsend criterion");
1650 }
1651
1652 // Compute the critical volume, critical area, the ionization volume and the electron appearance rate.
1653 timer.startEvent("Compute Vcr");
1654 const Real Vcr = this->computeCriticalVolumeTransient();
1655 const Real Acr = this->computeCriticalAreaTransient();
1656 const Real Vion = this->computeIonizationVolumeTransient(curVoltage);
1657 const Real Rdot = this->computeRdot(curVoltage);
1658 timer.stopEvent("Compute Vcr");
1659
1660 m_criticalVolume.emplace_back(curTime, Vcr);
1661 m_criticalArea.emplace_back(curTime, Acr);
1662 m_ionizationVolumeTransient.emplace_back(curTime, Vion);
1663 m_Rdot.emplace_back(curTime, Rdot);
1664
1665 // Compute the inception probability using the trapezoidal rule.
1666 if (m_Rdot.size() >= 2) {
1667 Real p = 0.0;
1668
1669 for (size_t i = 0; i < m_Rdot.size() - 1; i++) {
1670 const Real dt = m_Rdot[i + 1].first - m_Rdot[i].first;
1671
1672 p += 0.5 * dt * (m_Rdot[i + 1].second + m_Rdot[i].second);
1673 }
1674
1675 m_inceptionProbability.emplace_back(curTime, 1.0 - exp(-p));
1676 }
1677
1678 // Get the maximum K and T values
1679 timer.startEvent("Get max/min K");
1680 Real maxK = -std::numeric_limits<Real>::max();
1681 Real minK = +std::numeric_limits<Real>::max();
1682
1683 Real maxT = -std::numeric_limits<Real>::max();
1684 Real minT = +std::numeric_limits<Real>::max();
1685
1686 DataOps::getMaxMin(maxK, minK, m_inceptionIntegral, 0, m_amr->getMultiCutVofIterator(m_realm, m_phase));
1687 DataOps::getMaxMin(maxT, minT, m_townsendCriterion, 0, m_amr->getMultiCutVofIterator(m_realm, m_phase));
1688
1689 if (!m_fullIntegration) {
1690 maxK = std::min(maxK, m_inceptionK);
1691 maxT = std::min(maxT, 1.0);
1692 }
1693 m_maxK.emplace_back(m_time + a_dt, maxK);
1694 m_maxT.emplace_back(m_time + a_dt, maxT);
1695 timer.stopEvent("Get max/min K");
1696
1697 timer.startEvent("Write report");
1698 this->writeReportTransient();
1699 timer.stopEvent("Write report");
1700
1701 if (m_profile) {
1702 timer.eventReport(pout(), false);
1703 }
1704 }
1705 else {
1706 MayDay::Error("DischargeInceptionStepper::advance -- must have 'DischargeInceptionStepper.mode = transient'");
1707 }
1708
1709 return a_dt;
1710}
1711
1712template <typename P, typename F, typename C>
1713void
1715{
1716 CH_TIME("DischargeInceptionStepper::advanceIons");
1717 if (m_verbosity > 5) {
1718 pout() << "DischargeInceptionStepper::advanceIons" << endl;
1719 }
1720
1721 EBAMRCellData& phi = m_ionSolver->getPhi();
1722
1723 // Use an outflow BC for transport. Note that extrapolateAdvectiveFluxToEB computes
1724 // -n.(phi * v). Since we compute phi^(k+1) = phi^k - dt*sum(fluxes) this means that a negative
1725 // flux is an incoming flux.
1726 EBAMRIVData& ebFlux = m_ionSolver->getEbFlux();
1727 m_ionSolver->extrapolateAdvectiveFluxToEB(ebFlux);
1728 DataOps::floor(ebFlux, 0.0, m_amr->getVofIterator(m_realm, m_phase));
1729
1730 switch (m_transportAlgorithm) {
1731 case TransportAlgorithm::Euler: {
1732 EBAMRCellData divJ;
1733 m_amr->allocate(divJ, m_realm, m_phase, 1.0);
1734
1735 m_ionSolver->computeDivJ(divJ, phi, 0.0, false, true, false);
1736
1737 DataOps::incr(phi, divJ, -a_dt);
1738
1739 break;
1740 }
1741 case TransportAlgorithm::Heun: {
1742 // Transient storage
1743 EBAMRCellData yp;
1744 EBAMRCellData k1;
1745 EBAMRCellData k2;
1746
1747 m_amr->allocate(yp, m_realm, m_phase, 1);
1748 m_amr->allocate(k1, m_realm, m_phase, 1);
1749 m_amr->allocate(k2, m_realm, m_phase, 1);
1750
1751 // Compute k1 coefficient
1752 m_ionSolver->computeDivJ(k1, phi, 0.0, false, true, false);
1753 DataOps::copy(yp, phi);
1754 DataOps::incr(yp, k1, -a_dt);
1755
1756 // Compute k2 coefficient and final state
1757 m_ionSolver->computeDivJ(k2, yp, 0.0, false, true, false);
1758 DataOps::incr(phi, k1, -0.5 * a_dt);
1759 DataOps::incr(phi, k2, -0.5 * a_dt);
1760
1761 break;
1762 }
1763 case TransportAlgorithm::ImExCTU: {
1764 const bool addEbFlux = true;
1765 const bool addDomainFlux = true;
1766
1767 // Transient storage
1768 EBAMRCellData k1;
1769 EBAMRCellData k2;
1770
1771 m_amr->allocate(k1, m_realm, m_phase, 1);
1772 m_amr->allocate(k2, m_realm, m_phase, 1);
1773
1774 m_ionSolver->computeDivF(k1, phi, a_dt, false, true, true);
1775
1776 DataOps::kappaScale(k1, m_amr->getVofIterator(m_realm, m_phase));
1777 DataOps::scale(k1, -1.0);
1778
1779 // Use k1 as the old solution.
1780 DataOps::copy(k2, phi);
1781
1782 // Do the Euler solve.
1783 m_ionSolver->advanceCrankNicholson(phi, k2, k1, a_dt);
1784
1785 break;
1786 }
1787 default: {
1788 MayDay::Error("DischargeInceptionStepper::advanceIons -- logic bust");
1789 }
1790 }
1791
1792 DataOps::floor(phi, 0.0, m_amr->getVofIterator(m_realm, m_phase));
1793
1794 m_amr->average(phi, m_realm, m_phase, Average::Conservative);
1795 m_amr->interpGhost(phi, m_realm, m_phase);
1796}
1797
1798template <typename P, typename F, typename C>
1799void
1800DischargeInceptionStepper<P, F, C>::synchronizeSolverTimes(const int a_step, const Real a_time, const Real a_dt)
1801{
1802 CH_TIME("DischargeInceptionStepper::synchronizeSolverTimes");
1803 if (m_verbosity > 5) {
1804 pout() << "DischargeInceptionStepper::synchronizeSolverTimes" << endl;
1805 }
1806
1807 m_timeStep = a_step;
1808 m_time = a_time;
1809 m_dt = a_dt;
1810
1811 m_fieldSolver->setTime(a_step, a_time, a_dt);
1812 m_tracerParticleSolver->setTime(a_step, a_time, a_dt);
1813 m_ionSolver->setTime(a_step, a_time, a_dt);
1814}
1815
1816template <typename P, typename F, typename C>
1817void
1819{
1820 CH_TIME("DischargeInceptionStepper::printStepReport");
1821#ifndef NDEBUG
1822 if (m_verbosity > 5) {
1823 pout() << "DischargeInceptionStepper::printStepReport" << endl;
1824 }
1825#endif
1826
1827 std::string timeStepMessage;
1828 switch (m_timeStepRestriction) {
1829 case TimeStepRestriction::Unknown: {
1830 timeStepMessage = "Unknown";
1831
1832 break;
1833 }
1834 case TimeStepRestriction::CDR: {
1835 timeStepMessage = "CFL";
1836
1837 break;
1838 }
1839 case TimeStepRestriction::VoltageCurve: {
1840 timeStepMessage = "Voltage curve";
1841
1842 break;
1843 }
1844 case TimeStepRestriction::MinHardcap: {
1845 timeStepMessage = "Min hardcap";
1846
1847 break;
1848 }
1849 case TimeStepRestriction::MaxHardcap: {
1850 timeStepMessage = "Max hardcap";
1851
1852 break;
1853 }
1854 default: {
1855 MayDay::Warning("DischargeInceptionStepper::printStepReport - logic bust");
1856
1857 break;
1858 }
1859 }
1860
1861 // clang-format off
1862 pout() << " ** Voltage = " << m_voltageCurve(m_time) << endl;
1863 pout() << " ** Crit. volume = " << m_criticalVolume.back().second << endl;
1864 pout() << " ** Inception probability = " << m_inceptionProbability.back().second << endl;
1865 pout() << " ** Time step restriction = " << "'" << timeStepMessage << "'" << endl;
1866 // clang-format on
1867}
1868
1869template <typename P, typename F, typename C>
1870void
1871DischargeInceptionStepper<P, F, C>::preRegrid(const int a_lmin, const int a_oldFinestLevel)
1872{
1873 CH_TIME("DischargeInceptionStepper::preRegrid");
1874 if (m_verbosity > 5) {
1875 pout() << "DischargeInceptionStepper::preRegrid" << endl;
1876 }
1877
1878 m_fieldSolver->preRegrid(a_lmin, a_oldFinestLevel);
1879 m_tracerParticleSolver->preRegrid(a_lmin, a_oldFinestLevel);
1880 m_ionSolver->preRegrid(a_lmin, a_oldFinestLevel);
1881
1882 m_amr->allocate(m_scratchHomo, m_realm, 1);
1883 m_amr->allocate(m_scratchInho, m_realm, 1);
1884
1885 m_amr->copyData(m_scratchHomo, m_potentialHomo);
1886 m_amr->copyData(m_scratchInho, m_potentialInho);
1887}
1888
1889template <typename P, typename F, typename C>
1890void
1891DischargeInceptionStepper<P, F, C>::regrid(const int a_lmin, const int a_oldFinestLevel, const int a_newFinestLevel)
1892{
1893 CH_TIME("DischargeInceptionStepper::regrid");
1894 if (m_verbosity > 5) {
1895 pout() << "DischargeInceptionStepper::regrid" << endl;
1896 }
1897
1898 // Regrid tracer particles and field
1899 m_fieldSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1900 m_tracerParticleSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1901 m_ionSolver->regrid(a_lmin, a_oldFinestLevel, a_newFinestLevel);
1902
1903 // Regrid electric field and potentials
1904 m_amr->reallocate(m_potential, a_lmin);
1905 m_amr->reallocate(m_potentialHomo, a_lmin);
1906 m_amr->reallocate(m_potentialInho, a_lmin);
1907
1908 m_amr->reallocate(m_electricField, a_lmin);
1909 m_amr->reallocate(m_electricFieldHomo, a_lmin);
1910 m_amr->reallocate(m_electricFieldInho, a_lmin);
1911
1912 m_amr->reallocate(m_gradAlpha, m_phase, a_lmin);
1913
1914 const EBCoarseToFineInterp::Type interpType = EBCoarseToFineInterp::ConservativeMinMod;
1915
1916 m_amr->interpToNewGrids(m_potentialHomo, m_scratchHomo, a_lmin, a_oldFinestLevel, a_newFinestLevel, interpType);
1917 m_amr->interpToNewGrids(m_potentialInho, m_scratchInho, a_lmin, a_oldFinestLevel, a_newFinestLevel, interpType);
1918
1919 switch (m_mode) {
1920 case Mode::Stationary: {
1921 m_amr->reallocate(m_inceptionVoltagePlus, m_phase, a_lmin);
1922 m_amr->reallocate(m_inceptionVoltageMinu, m_phase, a_lmin);
1923 m_amr->reallocate(m_streamerInceptionVoltagePlus, m_phase, a_lmin);
1924 m_amr->reallocate(m_streamerInceptionVoltageMinu, m_phase, a_lmin);
1925 m_amr->reallocate(m_townsendInceptionVoltagePlus, m_phase, a_lmin);
1926 m_amr->reallocate(m_townsendInceptionVoltageMinu, m_phase, a_lmin);
1927
1928 break;
1929 }
1930 case Mode::Transient: {
1931 m_amr->reallocate(m_inceptionIntegral, m_phase, a_lmin);
1932 m_amr->reallocate(m_townsendCriterion, m_phase, a_lmin);
1933 m_amr->reallocate(m_emissionRate, m_phase, a_lmin);
1934 m_amr->reallocate(m_backgroundIonization, m_phase, a_lmin);
1935 m_amr->reallocate(m_detachment, m_phase, a_lmin);
1936
1937 break;
1938 }
1939 default: {
1940 break;
1941 }
1942 }
1943
1944 // Solve for the inhomogeneous and homogeneous Poisson fields again
1945 this->solvePoisson();
1946
1947 // Re-compute the ion velocity and diffusion coefficients
1948 this->computeIonVelocity(m_voltageCurve(m_time));
1949 this->computeIonDiffusion(m_voltageCurve(m_time));
1950}
1951
1952template <typename P, typename F, typename C>
1953void
1955{
1956 CH_TIME("DischargeInceptionStepper::postRegrid");
1957 if (m_verbosity > 5) {
1958 pout() << "DischargeInceptionStepper::postRegrid" << endl;
1959 }
1960
1961 m_scratchHomo.clear();
1962 m_scratchInho.clear();
1963}
1964
1965template <typename P, typename F, typename C>
1966void
1967DischargeInceptionStepper<P, F, C>::setVoltageCurve(const std::function<Real(const Real& E)>& a_voltageCurve) noexcept
1968{
1969 CH_TIME("DischargeInceptionStepper::setVoltageCurve");
1970 if (m_verbosity > 5) {
1971 pout() << "DischargeInceptionStepper::setVoltageCurve" << endl;
1972 }
1973
1974 m_voltageCurve = a_voltageCurve;
1975}
1976
1977template <typename P, typename F, typename C>
1978void
1979DischargeInceptionStepper<P, F, C>::setRho(const std::function<Real(const RealVect& x)>& a_rho) noexcept
1980{
1981 CH_TIME("DischargeInceptionStepper::setRho");
1982 if (m_verbosity > 5) {
1983 pout() << "DischargeInceptionStepper::setRho" << endl;
1984 }
1985
1986 m_rho = a_rho;
1987}
1988
1989template <typename P, typename F, typename C>
1990void
1991DischargeInceptionStepper<P, F, C>::setSigma(const std::function<Real(const RealVect& x)>& a_sigma) noexcept
1992{
1993 CH_TIME("DischargeInceptionStepper::setSigma");
1994 if (m_verbosity > 5) {
1995 pout() << "DischargeInceptionStepper::setSigma" << endl;
1996 }
1997
1998 m_sigma = a_sigma;
1999}
2000
2001template <typename P, typename F, typename C>
2002void
2003DischargeInceptionStepper<P, F, C>::setIonDensity(const std::function<Real(const RealVect x)>& a_density) noexcept
2004{
2005 CH_TIME("DischargeInceptionStepper::setIonDensity");
2006 if (m_verbosity > 5) {
2007 pout() << "DischargeInceptionStepper::setIonDensity" << endl;
2008 }
2009
2010 m_initialIonDensity = a_density;
2011}
2012
2013template <typename P, typename F, typename C>
2014void
2015DischargeInceptionStepper<P, F, C>::setIonMobility(const std::function<Real(const Real x)>& a_mobility) noexcept
2016{
2017 CH_TIME("DischargeInceptionStepper::setIonMobility");
2018 if (m_verbosity > 5) {
2019 pout() << "DischargeInceptionStepper::setIonMobility" << endl;
2020 }
2021
2022 m_ionMobility = a_mobility;
2023}
2024
2025template <typename P, typename F, typename C>
2026void
2027DischargeInceptionStepper<P, F, C>::setIonDiffusion(const std::function<Real(const Real x)>& a_diffCo) noexcept
2028{
2029 CH_TIME("DischargeInceptionStepper::setIonDiffusion");
2030 if (m_verbosity > 5) {
2031 pout() << "DischargeInceptionStepper::setIonDiffusion" << endl;
2032 }
2033
2034 m_ionDiffusion = a_diffCo;
2035}
2036
2037template <typename P, typename F, typename C>
2038void
2040 const std::function<Real(const Real& E, const RealVect& x)>& a_alpha) noexcept
2041{
2042 CH_TIME("DischargeInceptionStepper::setAlpha");
2043 if (m_verbosity > 5) {
2044 pout() << "DischargeInceptionStepper::setAlpha" << endl;
2045 }
2046
2047 m_alpha = a_alpha;
2048}
2049
2050template <typename P, typename F, typename C>
2051void
2052DischargeInceptionStepper<P, F, C>::setEta(const std::function<Real(const Real& E, const RealVect& x)>& a_eta) noexcept
2053{
2054 CH_TIME("DischargeInceptionStepper::setEta");
2055 if (m_verbosity > 5) {
2056 pout() << "DischargeInceptionStepper::setEta" << endl;
2057 }
2058
2059 m_eta = a_eta;
2060}
2061
2062template <typename P, typename F, typename C>
2063const std::function<Real(const Real& E, const RealVect& x)>&
2065{
2066 return m_alpha;
2067}
2068
2069template <typename P, typename F, typename C>
2070const std::function<Real(const Real& E, const RealVect& x)>&
2072{
2073 return m_eta;
2074}
2075
2076template <typename P, typename F, typename C>
2077void
2079 const std::function<Real(const Real& E, const RealVect& x)>& a_backgroundRate) noexcept
2080{
2081 CH_TIME("DischargeInceptionStepper::setBackgroundRate");
2082 if (m_verbosity > 5) {
2083 pout() << "DischargeInceptionStepper::setBackgroundRate" << endl;
2084 }
2085
2086 m_backgroundRate = a_backgroundRate;
2087}
2088
2089template <typename P, typename F, typename C>
2090void
2092 const std::function<Real(const Real& E, const RealVect& x)>& a_detachmentRate) noexcept
2093{
2094 CH_TIME("DischargeInceptionStepper::setDetachmentRate");
2095 if (m_verbosity > 5) {
2096 pout() << "DischargeInceptionStepper::setDetachmentRate" << endl;
2097 }
2098
2099 m_detachmentRate = a_detachmentRate;
2100}
2101
2102template <typename P, typename F, typename C>
2103void
2105 const std::function<Real(const Real& E, const RealVect& x)>& a_currentDensity) noexcept
2106{
2107 CH_TIME("DischargeInceptionStepper::setFieldEmission");
2108 if (m_verbosity > 5) {
2109 pout() << "DischargeInceptionStepper::setFieldEmission" << endl;
2110 }
2111
2112 m_fieldEmission = a_currentDensity;
2113}
2114
2115template <typename P, typename F, typename C>
2116void
2118 const std::function<Real(const Real& E, const RealVect& x)>& a_coefficient) noexcept
2119{
2120 CH_TIME("DischargeInceptionStepper::setSecondaryEmission");
2121 if (m_verbosity > 5) {
2122 pout() << "DischargeInceptionStepper::setSecondaryEmission" << endl;
2123 }
2124
2125 m_secondaryEmission = a_coefficient;
2126}
2127
2128template <typename P, typename F, typename C>
2129Mode
2131{
2132 return m_mode;
2133}
2134
2135template <typename P, typename F, typename C>
2136void
2138{
2139 CH_TIME("DischargeInceptionStepper::seedUniformParticles");
2140 if (m_verbosity > 5) {
2141 pout() << "DischargeInceptionStepper::seedUniformParticles" << endl;
2142 }
2143
2144 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
2145 amrParticles.clearParticles();
2146
2147 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2148 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2149 const DataIterator& dit = dbl.dataIterator();
2150 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
2151
2152 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
2153
2154 const Real dx = m_amr->getDx()[lvl];
2155
2156 auto& levelParticles = amrParticles[lvl];
2157
2158 const int nbox = dit.size();
2159
2160#pragma omp parallel for schedule(runtime)
2161 for (int mybox = 0; mybox < nbox; mybox++) {
2162 const DataIndex& din = dit[mybox];
2163
2164 const EBISBox& ebisbox = ebisl[din];
2165 const BaseFab<bool>& validCells = validCellsLD[din];
2166
2167 ParticleSoA<P>& leaf = levelParticles[din];
2168
2169 auto regularKernel = [&](const IntVect& iv) -> void {
2170 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
2171 leaf.append(m_amr->getProbLo() + (0.5 * RealVect::Unit + RealVect(iv)) * dx, 0.0);
2172 }
2173 };
2174
2175 auto irregularKernel = [&](const VolIndex& vof) -> void {
2176 if (validCells(vof.gridIndex())) {
2177 leaf.append(m_amr->getProbLo() + Location::position(Location::Cell::Centroid, vof, ebisbox, dx), 0.0);
2178 }
2179 };
2180
2181 // Execute kernels over appropriate regions. Not vectorizable: per-cell append. Multi-cut N/A:
2182 // regular uses cell-CENTER, irregular uses CENTROID position.
2183 const Box cellBox = dbl[din];
2184 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
2185
2186 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
2187 BoxLoops::loop(vofit, irregularKernel);
2188
2189 // Back up the initial position into the rewind column (weight was set to zero on append).
2190 const double* const pos[SpaceDim] = {
2191 D_DECL(leaf.positionColumn(0), leaf.positionColumn(1), leaf.positionColumn(2))};
2192 double* const x0[SpaceDim] = {
2193 D_DECL(leaf.template column<&P::x0_x>(), leaf.template column<&P::x0_y>(), leaf.template column<&P::x0_z>())};
2194 ParticleLoops::loop(leaf, [&](const std::size_t i) {
2195 for (int dir = 0; dir < SpaceDim; dir++) {
2196 x0[dir][i] = pos[dir][i];
2197 }
2198 });
2199 }
2200 }
2201
2202 // Remove particles inside the EB.
2203 m_amr->removeCoveredParticlesIF(amrParticles, m_phase, 0.0);
2204
2205 amrParticles.remap();
2206}
2207
2208template <typename P, typename F, typename C>
2209void
2211{
2212 CH_TIME("DischargeInceptionStepper::seedIonizationParticles");
2213 if (m_verbosity > 5) {
2214 pout() << "DischargeInceptionStepper::seedIonizationParticles" << endl;
2215 }
2216
2217 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
2218 amrParticles.clearParticles();
2219
2220 // Scratch storages
2221 EBAMRCellData scratch;
2222 EBAMRCellData alphaMesh;
2223
2224 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
2225 m_amr->allocate(alphaMesh, m_realm, m_phase, 1);
2226
2227 this->superposition(scratch, a_voltage);
2228
2229 DataOps::setValue(alphaMesh, 0.0);
2230
2231 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2232 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2233 const DataIterator& dit = dbl.dataIterator();
2234 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
2235
2236 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
2237
2238 const Real dx = m_amr->getDx()[lvl];
2239 const RealVect probLo = m_amr->getProbLo();
2240
2241 auto& levelParticles = amrParticles[lvl];
2242
2243 const int nbox = dit.size();
2244
2245#pragma omp parallel for schedule(runtime)
2246 for (int mybox = 0; mybox < nbox; mybox++) {
2247 const DataIndex& din = dit[mybox];
2248
2249 const EBISBox& ebisbox = ebisl[din];
2250 const BaseFab<bool>& validCells = validCellsLD[din];
2251
2252 ParticleSoA<P>& leaf = levelParticles[din];
2253
2254 const EBCellFAB& electricField = (*scratch[lvl])[din];
2255 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
2256
2257 EBCellFAB& alpha = (*alphaMesh[lvl])[din];
2258 FArrayBox& alphaReg = alpha.getFArrayBox();
2259
2260 if (!ebisbox.isAllCovered()) {
2261
2262 auto regularKernel = [&](const IntVect& iv) -> void {
2263 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
2264
2265 const RealVect x = probLo + dx * (0.5 * RealVect::Unit + RealVect(iv));
2266 const RealVect EE = RealVect(
2267 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
2268
2269 const Real E = EE.vectorLength();
2270 const Real curAlpha = m_alpha(E, x);
2271 const Real curEta = m_eta(E, x);
2272
2273 if (curAlpha > curEta) {
2274 leaf.append(x, 0.0);
2275
2276 alphaReg(iv, 0) = curAlpha - curEta;
2277 }
2278 }
2279 };
2280
2281 auto irregularKernel = [&](const VolIndex& vof) -> void {
2282 const IntVect iv = vof.gridIndex();
2283
2284 if (validCells(iv, 0) && ebisbox.isIrregular(iv)) {
2285 const RealVect x = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
2286 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
2287 const Real E = EE.vectorLength();
2288
2289 const Real curAlpha = m_alpha(E, x);
2290 const Real curEta = m_eta(E, x);
2291
2292 if (curAlpha > curEta) {
2293 leaf.append(x, 0.0);
2294
2295 alpha(vof, 0) = curAlpha - curEta;
2296 }
2297 }
2298 };
2299
2300 // Execute kernels over appropriate regions. Not vectorizable: m_alpha/m_eta std::function
2301 // coefficients per cell + per-particle append + data-dependent branch. Multi-cut N/A (center vs centroid).
2302 const Box cellBox = dbl[din];
2303 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
2304
2305 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
2306 BoxLoops::loop(vofit, irregularKernel);
2307 }
2308
2309 // Back up the initial position into the rewind column (weight + grad-alpha columns are already
2310 // zero from the default-payload append).
2311 const double* const pos[SpaceDim] = {
2312 D_DECL(leaf.positionColumn(0), leaf.positionColumn(1), leaf.positionColumn(2))};
2313 double* const x0[SpaceDim] = {
2314 D_DECL(leaf.template column<&P::x0_x>(), leaf.template column<&P::x0_y>(), leaf.template column<&P::x0_z>())};
2315 ParticleLoops::loop(leaf, [&](const std::size_t i) {
2316 for (int dir = 0; dir < SpaceDim; dir++) {
2317 x0[dir][i] = pos[dir][i];
2318 }
2319 });
2320 }
2321 }
2322
2323 // Update ghost cells
2324 m_amr->arithmeticAverage(alphaMesh, m_realm, m_phase);
2325 m_amr->interpGhost(alphaMesh, m_realm, m_phase);
2326
2327 // Compute gradient
2328 m_amr->computeGradient(m_gradAlpha, alphaMesh, m_realm, m_phase);
2329
2330 // Update ghost cells in the gradient
2331 m_amr->arithmeticAverage(m_gradAlpha, m_realm, m_phase);
2332 m_amr->interpGhost(m_gradAlpha, m_realm, m_phase);
2333 m_amr->interpToCentroids(m_gradAlpha, m_realm, m_phase);
2334}
2335
2336template <typename P, typename F, typename C>
2337void
2339{
2340 CH_TIME("DischargeInceptionStepper::postInitialize");
2341 if (m_verbosity > 5) {
2342 pout() << "DischargeInceptionStepper::postInitialize" << endl;
2343 }
2344
2345 // Initialize the negative ion solver
2346 m_ionSolver->initialData();
2347 this->computeIonVelocity(m_voltageCurve(m_time));
2348 this->computeIonDiffusion(m_voltageCurve(m_time));
2349
2350 switch (m_mode) {
2351 case Mode::Stationary: {
2352 this->computeInceptionIntegralStationary();
2353 this->computeTownsendCriterionStationary();
2354 this->computeCriticalVolumeStationary();
2355 this->computeCriticalAreaStationary();
2356 this->computeIonizationVolumeStationary();
2357 this->computeInceptionVoltageVolume();
2358
2359 // Compute background ionization and field emission on the mesh. Mostly for plotting.
2360 if (m_plotBackgroundIonization) {
2361 this->computeBackgroundIonizationStationary();
2362 }
2363
2364 if (m_plotDetachment) {
2365 this->computeDetachmentStationary();
2366 }
2367
2368 if (m_plotFieldEmission) {
2369 this->computeFieldEmissionStationary();
2370 }
2371
2372 this->writeReportStationary();
2373
2374 break;
2375 }
2376 case Mode::Transient: {
2377
2378 // Seed tracer particles and set velocity
2379 this->seedIonizationParticles(m_voltageCurve(m_time));
2380 this->computeInceptionIntegralTransient(m_voltageCurve(m_time));
2381
2382 // Evaluate the Townsend criterion if the user asks for it.
2383 if (m_evaluateTownsend) {
2384 this->seedIonizationParticles(m_voltageCurve(m_time));
2385 this->computeTownsendCriterionTransient(m_voltageCurve(m_time));
2386 }
2387
2388 // Get the maximum K and T values
2389 Real maxK = -std::numeric_limits<Real>::max();
2390 Real minK = +std::numeric_limits<Real>::max();
2391
2392 Real maxT = -std::numeric_limits<Real>::max();
2393 Real minT = +std::numeric_limits<Real>::max();
2394
2395 DataOps::getMaxMin(maxK, minK, m_inceptionIntegral, 0, m_amr->getMultiCutVofIterator(m_realm, m_phase));
2396 DataOps::getMaxMin(maxT, minT, m_townsendCriterion, 0, m_amr->getMultiCutVofIterator(m_realm, m_phase));
2397 if (!m_fullIntegration) {
2398 maxK = std::min(maxK, m_inceptionK);
2399 maxT = std::min(maxT, 1.0);
2400 }
2401
2402 m_Rdot.emplace_back(m_time, this->computeRdot(m_voltageCurve(m_time)));
2403 m_criticalVolume.emplace_back(m_time, this->computeCriticalVolumeTransient());
2404 m_criticalArea.emplace_back(m_time, this->computeCriticalAreaTransient());
2405 m_ionizationVolumeTransient.emplace_back(m_time, this->computeIonizationVolumeTransient(m_voltageCurve(m_time)));
2406 m_inceptionProbability.emplace_back(m_time, 0.0);
2407 m_maxK.emplace_back(m_time, maxK);
2408 m_maxT.emplace_back(m_time, maxT);
2409
2410 break;
2411 }
2412 default: {
2413 break;
2414 }
2415 }
2416}
2417
2418template <typename P, typename F, typename C>
2419void
2421{
2422 CH_TIME("DischargeInceptionStepper::interpolateGradAlphaToParticles");
2423 if (m_verbosity > 5) {
2424 pout() << "DischargeInceptionStepper::interpolateGradAlphaToParticles" << endl;
2425 }
2426
2427 // Interpolate onto the particle weight structure
2428 m_amr->interpolateParticles<D_DECL(&P::ga_x, &P::ga_y, &P::ga_z)>(m_tracerParticleSolver->getParticles(),
2429 m_realm,
2430 m_phase,
2431 m_gradAlpha,
2432 m_tracerParticleSolver->getInterpolationType(),
2433 true);
2434}
2435
2436template <typename P, typename F, typename C>
2437void
2439 const Real a_voltage) noexcept
2440{
2441 CH_TIME("DischargeInceptionStepper::computeInceptionIntegral");
2442 if (m_verbosity > 5) {
2443 pout() << "DischargeInceptionStepper::computeInceptionIntegral" << endl;
2444 }
2445
2446 // TLDR: For this integration we compute the value of the inception integral
2447 //
2448 // K = int alpha_eff(E)dl
2449 //
2450 // The integration runs from the start position of the particle and along a field line until either the
2451 // particle leaves the domain or alpha < 0.0.
2452 //
2453 // For the Euler rule we get
2454 //
2455 // T += alpha_eff(E(x)) * dx
2456 //
2457 // For the trapezoidal rule we get
2458 //
2459 // T += 0.5 * dx * [alpha_eff(E(x)) + alpha_eff(E(x+dx))]
2460
2461 // Reset the particles used for field line integration
2462 this->seedIonizationParticles(a_voltage);
2463 this->resetTracerParticles();
2464
2465 // Switch between algorithms.
2466 switch (m_inceptionAlgorithm) {
2467 case IntegrationAlgorithm::Euler: {
2468 this->inceptionIntegrateEuler(a_voltage);
2469
2470 break;
2471 }
2472 case IntegrationAlgorithm::Trapezoidal: {
2473 this->inceptionIntegrateTrapezoidal(a_voltage);
2474
2475 break;
2476 }
2477 default: {
2478 MayDay::Error("DischargeInceptionStepper::computeInceptionIntegral -- logic bust");
2479
2480 break;
2481 }
2482 }
2483
2484 // Deposit the particles on the mesh and get the max/min values.
2485 m_tracerParticleSolver->deposit(a_inceptionIntegral);
2486
2487 m_amr->conservativeAverage(a_inceptionIntegral, m_realm, m_phase);
2488 m_amr->interpGhost(a_inceptionIntegral, m_realm, m_phase);
2489}
2490
2491template <typename P, typename F, typename C>
2492void
2494{
2495 CH_TIME("DischargeInceptionStepper::computeInceptionIntegralStationary");
2496 if (m_verbosity > 5) {
2497 pout() << "DischargeInceptionStepper::computeInceptionIntegralStationary" << endl;
2498 }
2499
2500 constexpr int maxIter = 50;
2501
2502 // Get the maximum field (in the gas phase) with the electrodes at 1V
2503 Real maxField = 0.0;
2504 Real minField = 0.0;
2505
2506 EBAMRCellData gasField = m_amr->alias(phase::gas, m_electricField);
2507
2508 this->superposition(gasField, 1.0);
2509
2510 DataOps::getMaxMinNorm(maxField, minField, gasField, m_amr->getMultiCutVofIterator(m_realm, m_phase));
2511
2512 // Compute the critical field and the minimum possible inception voltage.
2513 const Real Ecrit = this->getCriticalField();
2514
2515 Real curVoltage = m_relativeDeltaU * Ecrit / maxField;
2516 Real curMaxK = 0.0;
2517 int curIter = 0;
2518
2519 m_inceptionIntegralPlus.resize(0);
2520 m_inceptionIntegralMinu.resize(0);
2521
2522 // We use K1, K2, U2, U1 to extrapolate to a next voltage.
2523 Real K1 = 0.0;
2524 Real K2 = 0.0;
2525 Real U1 = Ecrit / maxField;
2526 Real U2 = curVoltage;
2527
2528 while (curMaxK < m_maxKLimit && curIter < maxIter) {
2529 EBAMRCellData Kplus;
2530 EBAMRCellData Kminu;
2531
2532 m_amr->allocate(Kplus, m_realm, m_phase, 1);
2533 m_amr->allocate(Kminu, m_realm, m_phase, 1);
2534
2535 DataOps::setValue(Kplus, 0.0);
2536 DataOps::setValue(Kminu, 0.0);
2537
2538 m_inceptionIntegralPlus.push_back(Kplus);
2539 m_inceptionIntegralMinu.push_back(Kminu);
2540
2541 this->computeInceptionIntegral(Kplus, +curVoltage);
2542 this->computeInceptionIntegral(Kminu, -curVoltage);
2543
2544 // Get the max and min values of K.
2545 Real maxKPlus = -std::numeric_limits<Real>::max();
2546 Real maxKMinu = -std::numeric_limits<Real>::max();
2547
2548 RealVect maxKPlusPos = RealVect::Zero;
2549 RealVect maxKMinuPos = RealVect::Zero;
2550
2551 this->getMaxValueAndLocation(maxKPlus, maxKPlusPos, Kplus);
2552 this->getMaxValueAndLocation(maxKMinu, maxKMinuPos, Kminu);
2553
2554 // Populate the relevant data containers.
2555 m_KPlusValues.emplace_back(std::make_tuple(curVoltage, maxKPlus, maxKPlusPos));
2556 m_KMinuValues.emplace_back(std::make_tuple(curVoltage, maxKMinu, maxKMinuPos));
2557
2558 curMaxK = std::max(maxKPlus, maxKMinu);
2559
2560 // Figure out the next voltage. User linear extrapolation near the first point and log-linear otherwise.
2561 K2 = curMaxK;
2562 U2 = curVoltage;
2563
2564 Real nextVoltage;
2565 if (K1 <= 0.0) {
2566 const Real dKdU = (K2 - K1) / (U2 - U1);
2567
2568 nextVoltage = U1 + ((K2 - K1) + m_deltaK) / dKdU;
2569 }
2570 else {
2571 const Real dKdU = std::log(K2 / K1) / (U2 - U1);
2572
2573 nextVoltage = U1 + std::log((K2 + m_deltaK) / K1) / dKdU;
2574 }
2575
2576 K1 = K2;
2577 U1 = U2;
2578
2579 // Step to next voltage.
2580 curVoltage = std::min(nextVoltage, curVoltage * m_relativeDeltaU);
2581 curIter = curIter + 1;
2582
2583 if (!m_fullIntegration && (std::abs(curMaxK - m_inceptionK) <= 1E-3)) {
2584 break;
2585 }
2586 }
2587
2588 // Populate the voltage sweep table
2589 m_voltageSweeps.resize(0);
2590 for (int i = 0; i < m_KPlusValues.size(); i++) {
2591 m_voltageSweeps.push_back(std::get<0>(m_KPlusValues[i]));
2592 }
2593}
2594
2595template <typename P, typename F, typename C>
2596void
2598{
2599 CH_TIME("DischargeInceptionStepper::computeInceptionIntegralTransient");
2600 if (m_verbosity > 5) {
2601 pout() << "DischargeInceptionStepper::computeInceptionIntegralTransient" << endl;
2602 }
2603
2604 // Compute K-value on each particle using specified algorithm.
2605 switch (m_inceptionAlgorithm) {
2606 case IntegrationAlgorithm::Euler: {
2607 this->inceptionIntegrateEuler(a_voltage);
2608
2609 break;
2610 }
2611 case IntegrationAlgorithm::Trapezoidal: {
2612 this->inceptionIntegrateTrapezoidal(a_voltage);
2613
2614 break;
2615 }
2616 default: {
2617 MayDay::Error("DischargeInceptionStepper::computeInceptionIntegralTransient - logic bust");
2618
2619 break;
2620 }
2621 }
2622
2623 // Deposit the particles onto m_inceptionIntegral
2624 m_tracerParticleSolver->deposit(m_inceptionIntegral);
2625
2626 m_amr->conservativeAverage(m_inceptionIntegral, m_realm, m_phase);
2627 m_amr->interpGhost(m_inceptionIntegral, m_realm, m_phase);
2628}
2629
2630template <typename P, typename F, typename C>
2631void
2633{
2634 CH_TIME("DischargeInceptionStepper::inceptionIntegrateEuler");
2635 if (m_verbosity > 5) {
2636 pout() << "DischargeInceptionStepper::inceptionIntegrateEuler" << endl;
2637 }
2638
2639 const RealVect probLo = m_amr->getProbLo();
2640 const RealVect probHi = m_amr->getProbHi();
2641
2642 // Allocate a data holder for holding the processed particles. This
2643 // will be faster because then we only have to iterate through the
2644 // particles that are actually still moving.
2645 ParticleContainer<P> amrProcessedParticles;
2646 m_amr->allocate(amrProcessedParticles, m_realm);
2647
2648 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
2649
2650 m_tracerParticleSolver->remap();
2651
2652 size_t particlesBefore = 0;
2653
2654 if (m_debug) {
2655 particlesBefore = amrParticles.getNumberOfValidParticlesGlobal();
2656 }
2657
2658 // Allocate something that holds the velocity of the electrons
2659 EBAMRCellData scratch;
2660 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
2661 this->superposition(scratch, a_voltage);
2662 DataOps::scale(scratch, -1.0);
2663
2664 m_tracerParticleSolver->setVelocity(scratch);
2665 m_tracerParticleSolver->interpolateVelocities();
2666
2667 while (amrParticles.getNumberOfValidParticlesGlobal() > 0) {
2668
2669 this->interpolateGradAlphaToParticles();
2670
2671 // Euler integration.
2672 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2673 const Real dx = m_amr->getDx()[lvl];
2674
2675 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2676 const DataIterator& dit = dbl.dataIterator();
2677
2678 const int nbox = dit.size();
2679
2680#pragma omp parallel for schedule(runtime)
2681 for (int mybox = 0; mybox < nbox; mybox++) {
2682 const DataIndex& din = dit[mybox];
2683
2684 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
2685 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
2686
2687 // Column pointers are captured once: remove() is an in-place swap-pop and setting positions/weights
2688 // is in-place, so these stay valid across the loop (only processedLeaf grows, internally).
2689 double* const w = solverLeaf.weightColumn();
2690 double* const pos[SpaceDim] = {
2691 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
2692 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
2693 solverLeaf.template column<&P::v_y>(),
2694 solverLeaf.template column<&P::v_z>())};
2695 const ParticleReal* const ga[SpaceDim] = {D_DECL(solverLeaf.template column<&P::ga_x>(),
2696 solverLeaf.template column<&P::ga_y>(),
2697 solverLeaf.template column<&P::ga_z>())};
2698
2699 std::size_t i = 0;
2700 while (i < solverLeaf.size()) {
2701 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
2702 const RealVect vec(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
2703 const Real v = vec.vectorLength();
2704 const Real E = v;
2705 const Real alpha = m_alpha(E, x);
2706 const Real eta = m_eta(E, x);
2707 const Real alphaEff = alpha - eta;
2708 const Real tol = 1E-10;
2709 const Real gradAlpha = tol + RealVect(D_DECL(ga[0][i], ga[1][i], ga[2][i])).vectorLength();
2710
2711 // Select a step size equal to the avalanche length, but never exceed the physical and grid hardcaps
2712 Real deltaX;
2713 deltaX = std::min(m_alphaDx / (tol + std::abs(alphaEff)), m_gradAlphaDx * std::abs(alphaEff / gradAlpha));
2714 deltaX = std::max(deltaX, m_minGridDx * dx);
2715 deltaX = std::min(deltaX, m_maxGridDx * dx);
2716 deltaX = std::min(deltaX, m_maxPhysDx);
2717 deltaX = std::max(deltaX, m_minPhysDx);
2718
2719 const Real dt = deltaX / v;
2720 const RealVect newPos = x + dt * vec;
2721 const Real delta = (newPos - x).vectorLength();
2722
2723 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
2724 const bool insideEB = this->particleInsideEB(newPos);
2725
2726 Real s = 0.0;
2727
2728 if (alphaEff < 0.0) {
2729 processedLeaf.appendParticle(solverLeaf, i);
2730 solverLeaf.remove(i);
2731 }
2732 else if (insideEB) {
2733 // If the particle struck the EB or domain we finish off the integration with a partial step
2734 const RefCountedPtr<BaseIF>& impFunc = m_amr->getBaseImplicitFunction(m_phase);
2735
2736 if (ParticleOps::ebIntersectionBisect(impFunc, x, newPos, m_minGridDx * dx, s)) {
2737 w[i] = w[i] + s * delta * alphaEff;
2738 }
2739
2740 processedLeaf.appendParticle(solverLeaf, i);
2741 solverLeaf.remove(i);
2742 }
2743 else if (outsideDomain) {
2744 if (ParticleOps::domainIntersection(x, newPos, m_amr->getProbLo(), m_amr->getProbHi(), s)) {
2745 w[i] = w[i] + s * delta * alphaEff;
2746 }
2747
2748 processedLeaf.appendParticle(solverLeaf, i);
2749 solverLeaf.remove(i);
2750 }
2751 else {
2752 for (int dir = 0; dir < SpaceDim; dir++) {
2753 pos[dir][i] = newPos[dir];
2754 }
2755 w[i] = w[i] + deltaX * alphaEff;
2756
2757 i++;
2758 }
2759 }
2760
2761 // Transfer particles that completed their integration.
2762 if (!m_fullIntegration) {
2763 std::size_t j = 0;
2764 while (j < solverLeaf.size()) {
2765 if (w[j] >= m_inceptionK) {
2766 processedLeaf.appendParticle(solverLeaf, j);
2767 solverLeaf.remove(j);
2768 }
2769 else {
2770 j++;
2771 }
2772 }
2773 }
2774 }
2775 }
2776
2777 // Update velocities.
2778 m_tracerParticleSolver->remap();
2779 m_tracerParticleSolver->interpolateVelocities();
2780 }
2781
2782 ParticleOps::copyDestructive(amrParticles, amrProcessedParticles);
2783
2784 // Truncate the weights if we didn't run full integration
2785 if (!m_fullIntegration) {
2786 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2787 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2788 const DataIterator& dit = dbl.dataIterator();
2789
2790 const int nbox = dit.size();
2791
2792#pragma omp parallel for schedule(runtime)
2793 for (int mybox = 0; mybox < nbox; mybox++) {
2794 const DataIndex& din = dit[mybox];
2795
2796 ParticleSoA<P>& leaf = amrParticles[lvl][din];
2797
2798 double* const w = leaf.weightColumn();
2799
2800 ParticleLoops::loop(leaf, [&](const std::size_t i) {
2801 w[i] = std::min(m_inceptionK, w[i]);
2802 });
2803 }
2804 }
2805 }
2806
2807 this->rewindTracerParticles();
2808
2809 size_t particlesAfter = 0;
2810
2811 if (m_debug) {
2812 particlesAfter = amrParticles.getNumberOfValidParticlesGlobal();
2813 }
2814
2815 if (particlesBefore != particlesAfter) {
2816 MayDay::Warning("DischargeInceptionStepper::inceptionIntegrateEuler - lost/gained particles!");
2817 }
2818}
2819
2820template <typename P, typename F, typename C>
2821void
2823{
2824 CH_TIME("DischargeInceptionStepper::inceptionIntegrateTrapezoidal");
2825 if (m_verbosity > 5) {
2826 pout() << "DischargeInceptionStepper::inceptionIntegrateTrapezoidal" << endl;
2827 }
2828
2829 // TLDR: We move the particle using Heun's method.
2830 //
2831 // p^(k+1) = p^k + 0.5 * dt * [v(p^k) + v(p^l)]
2832 //
2833 // where p^l = p^k + dt * v(p^k). We will set dt = d/|v(p^k)|. Once we have the
2834 // particle endpoints we can compute the contribution to the inception integral
2835 // by using the trapezoidal (quadrature) rule
2836 //
2837 // T += 0.5 * dx * [alpha_eff(E(p^k)) + alpha_eff(E(p^(k+1)))],
2838 //
2839 // where dx = |p^(k+1) - p^k|.
2840
2841 const RealVect probLo = m_amr->getProbLo();
2842 const RealVect probHi = m_amr->getProbHi();
2843
2844 // Allocate a data holder for holding the processed particles. This
2845 // will be faster because then we only have to iterate through the
2846 // particles that are actually still moving.
2847 ParticleContainer<P> amrProcessedParticles;
2848 m_amr->allocate(amrProcessedParticles, m_realm);
2849
2850 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
2851
2852 m_tracerParticleSolver->remap();
2853
2854 size_t particlesBefore = 0;
2855
2856 if (m_debug) {
2857 particlesBefore = amrParticles.getNumberOfValidParticlesGlobal();
2858 }
2859
2860 // Allocate something that holds the velocity of the electrons
2861 EBAMRCellData scratch;
2862 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
2863 this->superposition(scratch, a_voltage);
2864 DataOps::scale(scratch, -1.0);
2865
2866 m_tracerParticleSolver->setVelocity(scratch);
2867 m_tracerParticleSolver->interpolateVelocities();
2868
2869 while (amrParticles.getNumberOfValidParticlesGlobal() > 0) {
2870
2871 this->interpolateGradAlphaToParticles();
2872
2873 // Euler stage.
2874 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2875 const Real dx = m_amr->getDx()[lvl];
2876
2877 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2878 const DataIterator& dit = dbl.dataIterator();
2879
2880 const int nbox = dit.size();
2881
2882#pragma omp parallel for schedule(runtime)
2883 for (int mybox = 0; mybox < nbox; mybox++) {
2884 const DataIndex& din = dit[mybox];
2885
2886 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
2887 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
2888
2889 double* const w = solverLeaf.weightColumn();
2890 double* const pos[SpaceDim] = {
2891 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
2892 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
2893 solverLeaf.template column<&P::v_y>(),
2894 solverLeaf.template column<&P::v_z>())};
2895 const ParticleReal* const ga[SpaceDim] = {D_DECL(solverLeaf.template column<&P::ga_x>(),
2896 solverLeaf.template column<&P::ga_y>(),
2897 solverLeaf.template column<&P::ga_z>())};
2898 ParticleReal* const vk[SpaceDim] = {D_DECL(solverLeaf.template column<&P::vk_x>(),
2899 solverLeaf.template column<&P::vk_y>(),
2900 solverLeaf.template column<&P::vk_z>())};
2901 ParticleReal* const alphaEffCol = solverLeaf.template column<&P::alphaEff>();
2902 ParticleReal* const dtCol = solverLeaf.template column<&P::dtStep>();
2903
2904 std::size_t i = 0;
2905 while (i < solverLeaf.size()) {
2906 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
2907 const RealVect vec(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
2908 const Real v = vec.vectorLength();
2909 const Real E = v;
2910 const Real alpha = m_alpha(E, x);
2911 const Real eta = m_eta(E, x);
2912 const Real alphaEff = alpha - eta;
2913 const Real tol = 1E-10;
2914 const Real gradAlpha = tol + RealVect(D_DECL(ga[0][i], ga[1][i], ga[2][i])).vectorLength();
2915
2916 // Select a step size equal to the avalanche length, but never exceed the physical and grid hardcaps
2917 Real deltaX;
2918 deltaX = std::min(m_alphaDx / (tol + std::abs(alphaEff)), m_gradAlphaDx * std::abs(alphaEff / gradAlpha));
2919 deltaX = std::max(deltaX, m_minGridDx * dx);
2920 deltaX = std::min(deltaX, m_maxGridDx * dx);
2921 deltaX = std::min(deltaX, m_maxPhysDx);
2922 deltaX = std::max(deltaX, m_minPhysDx);
2923
2924 const Real dt = deltaX / v;
2925 const RealVect newPos = x + dt * vec;
2926 const Real delta = (newPos - x).vectorLength();
2927
2928 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
2929 const bool insideEB = this->particleInsideEB(newPos);
2930
2931 // If particle hit the EB or domain we finish off with a partial Euler step
2932 Real s = 0.0;
2933
2934 if (alphaEff < 0.0) {
2935 processedLeaf.appendParticle(solverLeaf, i);
2936 solverLeaf.remove(i);
2937 }
2938 else if (insideEB) {
2939 const RefCountedPtr<BaseIF>& impFunc = m_amr->getBaseImplicitFunction(m_phase);
2940
2941 if (ParticleOps::ebIntersectionBisect(impFunc, x, newPos, m_minGridDx * dx, s)) {
2942 w[i] = w[i] + s * delta * alphaEff;
2943 }
2944
2945 processedLeaf.appendParticle(solverLeaf, i);
2946 solverLeaf.remove(i);
2947 }
2948 else if (outsideDomain) {
2949 if (ParticleOps::domainIntersection(x, newPos, m_amr->getProbLo(), m_amr->getProbHi(), s)) {
2950 w[i] = w[i] + s * delta * alphaEff;
2951 }
2952
2953 processedLeaf.appendParticle(solverLeaf, i);
2954 solverLeaf.remove(i);
2955 }
2956 else {
2957 // Do an Euler step, storing alpha(p^k), v(p^k), and the time step size.
2958 alphaEffCol[i] = alphaEff;
2959 dtCol[i] = dt;
2960 for (int dir = 0; dir < SpaceDim; dir++) {
2961 vk[dir][i] = vec[dir];
2962 pos[dir][i] = newPos[dir];
2963 }
2964
2965 i++;
2966 }
2967 }
2968 }
2969 }
2970
2971 // Remap and update velocities.
2972 m_tracerParticleSolver->remap();
2973 m_tracerParticleSolver->interpolateVelocities();
2974
2975 // Second stage.
2976 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
2977 const Real dx = m_amr->getDx()[lvl];
2978
2979 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
2980 const DataIterator& dit = dbl.dataIterator();
2981
2982 const int nbox = dit.size();
2983
2984#pragma omp parallel for schedule(runtime)
2985 for (int mybox = 0; mybox < nbox; mybox++) {
2986 const DataIndex& din = dit[mybox];
2987
2988 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
2989 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
2990
2991 double* const w = solverLeaf.weightColumn();
2992 double* const pos[SpaceDim] = {
2993 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
2994 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
2995 solverLeaf.template column<&P::v_y>(),
2996 solverLeaf.template column<&P::v_z>())};
2997 const ParticleReal* const vk[SpaceDim] = {D_DECL(solverLeaf.template column<&P::vk_x>(),
2998 solverLeaf.template column<&P::vk_y>(),
2999 solverLeaf.template column<&P::vk_z>())};
3000 const ParticleReal* const alphaEffCol = solverLeaf.template column<&P::alphaEff>();
3001 const ParticleReal* const dtCol = solverLeaf.template column<&P::dtStep>();
3002
3003 std::size_t i = 0;
3004 while (i < solverLeaf.size()) {
3005 const Real dt = dtCol[i];
3006 const RealVect vkv(D_DECL(vk[0][i], vk[1][i], vk[2][i]));
3007 const RealVect vk1(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
3008 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
3009 const Real E = vk1.vectorLength();
3010
3011 // Note the weird subtraction since the position was updated to p^k + dt * v^k.
3012 const RealVect oldPos = x - dt * vkv;
3013 const RealVect newPos = oldPos + 0.5 * dt * (vkv + vk1);
3014
3015 // Compute new alpha.
3016 const Real alphak = alphaEffCol[i];
3017 const Real alphak1 = m_alpha(E, x) - m_eta(E, x);
3018 const Real delta = (newPos - oldPos).vectorLength();
3019
3020 // Stop integration for particles that move into regions alpha < 0.0,
3021 // inside the EB or outside of the domain.
3022 const bool negativeAlpha = (alphak + alphak1) < 0.0;
3023 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
3024 const bool insideEB = this->particleInsideEB(newPos);
3025
3026 // If the particle wound up inside the EB we finish off the integration with a partial Euler step
3027 Real s = 0.0;
3028
3029 if (negativeAlpha) {
3030 processedLeaf.appendParticle(solverLeaf, i);
3031 solverLeaf.remove(i);
3032 }
3033 else if (insideEB) {
3034 const RefCountedPtr<BaseIF>& impFunc = m_amr->getBaseImplicitFunction(m_phase);
3035
3036 if (ParticleOps::ebIntersectionBisect(impFunc, oldPos, newPos, m_minGridDx * dx, s)) {
3037 w[i] = w[i] + s * delta * alphak;
3038 }
3039
3040 processedLeaf.appendParticle(solverLeaf, i);
3041 solverLeaf.remove(i);
3042 }
3043 else if (outsideDomain) {
3044 if (ParticleOps::domainIntersection(oldPos, newPos, m_amr->getProbLo(), m_amr->getProbHi(), s)) {
3045 w[i] = w[i] + s * delta * alphak;
3046 }
3047
3048 processedLeaf.appendParticle(solverLeaf, i);
3049 solverLeaf.remove(i);
3050 }
3051 else {
3052 for (int dir = 0; dir < SpaceDim; dir++) {
3053 pos[dir][i] = newPos[dir];
3054 }
3055 w[i] = w[i] + 0.5 * delta * (alphak + alphak1);
3056
3057 i++;
3058 }
3059 }
3060
3061 // Transfer particles that completed their integration (if we're doing partial integration)
3062 if (!m_fullIntegration) {
3063 std::size_t j = 0;
3064 while (j < solverLeaf.size()) {
3065 if (w[j] >= m_inceptionK) {
3066 processedLeaf.appendParticle(solverLeaf, j);
3067 solverLeaf.remove(j);
3068 }
3069 else {
3070 j++;
3071 }
3072 }
3073 }
3074 }
3075 }
3076
3077 // Remap and update velocities.
3078 m_tracerParticleSolver->remap();
3079 m_tracerParticleSolver->interpolateVelocities();
3080 }
3081
3082 // Copy processed particles over to the solver particles.
3083 ParticleOps::copyDestructive(amrParticles, amrProcessedParticles);
3084
3085 // Truncate the weights if we didn't run full integration
3086 if (!m_fullIntegration) {
3087 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3088 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3089 const DataIterator& dit = dbl.dataIterator();
3090
3091 const int nbox = dit.size();
3092
3093#pragma omp parallel for schedule(runtime)
3094 for (int mybox = 0; mybox < nbox; mybox++) {
3095 const DataIndex& din = dit[mybox];
3096
3097 ParticleSoA<P>& leaf = amrParticles[lvl][din];
3098
3099 double* const w = leaf.weightColumn();
3100
3101 ParticleLoops::loop(leaf, [&](const std::size_t i) {
3102 w[i] = std::min(m_inceptionK, w[i]);
3103 });
3104 }
3105 }
3106 }
3107
3108 this->rewindTracerParticles();
3109}
3110
3111template <typename P, typename F, typename C>
3112void
3114{
3115 CH_TIME("DischargeInceptionStepper::computeTownsendCriterionStationary");
3116 if (m_verbosity > 5) {
3117 pout() << "DischargeInceptionStepper::computeTownsendCriterionStationary" << endl;
3118 }
3119
3120 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
3121
3122 m_townsendCriterionPlus.resize(0);
3123 m_townsendCriterionMinu.resize(0);
3124
3125 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
3126 const Real voltage = m_voltageSweeps[i];
3127
3128 const EBAMRCellData KPlus = m_inceptionIntegralPlus[i];
3129 const EBAMRCellData KMinu = m_inceptionIntegralMinu[i];
3130
3131 EBAMRCellData townsendCriterionPlus;
3132 EBAMRCellData townsendCriterionMinu;
3133
3134 EBAMRCellData expKPlus;
3135 EBAMRCellData expKMinu;
3136
3137 m_amr->allocate(townsendCriterionPlus, m_realm, m_phase, 1);
3138 m_amr->allocate(townsendCriterionMinu, m_realm, m_phase, 1);
3139
3140 m_amr->allocate(expKPlus, m_realm, m_phase, 1);
3141 m_amr->allocate(expKMinu, m_realm, m_phase, 1);
3142
3143 DataOps::setValue(townsendCriterionPlus, 0.0);
3144 DataOps::setValue(townsendCriterionMinu, 0.0);
3145
3146 DataOps::setValue(expKPlus, 0.0);
3147 DataOps::setValue(expKMinu, 0.0);
3148
3149 Real maxTPlus = 0.0;
3150 Real maxTMinu = 0.0;
3151
3152 RealVect maxTPlusPos = RealVect::Zero;
3153 RealVect maxTMinuPos = RealVect::Zero;
3154
3155 if (m_evaluateTownsend) {
3156
3157 // Positive polarity.
3158 this->resetTracerParticles();
3159
3160 switch (m_inceptionAlgorithm) {
3161 case IntegrationAlgorithm::Euler: {
3162 this->townsendTrackEuler(voltage);
3163
3164 break;
3165 }
3166 case IntegrationAlgorithm::Trapezoidal: {
3167 this->townsendTrackTrapezoidal(voltage);
3168
3169 break;
3170 }
3171 default: {
3172 MayDay::Error("DischargeInceptionStepper::computeTownsendCriterionStationary -- logic bust");
3173
3174 break;
3175 }
3176 }
3177
3178 m_tracerParticleSolver->deposit(townsendCriterionPlus);
3179
3180 m_amr->conservativeAverage(townsendCriterionPlus, m_realm, m_phase);
3181 m_amr->interpGhost(townsendCriterionPlus, m_realm, m_phase);
3182
3183 // Negative polarity.
3184 this->resetTracerParticles();
3185
3186 switch (m_inceptionAlgorithm) {
3187 case IntegrationAlgorithm::Euler: {
3188 this->townsendTrackEuler(-voltage);
3189
3190 break;
3191 }
3192 case IntegrationAlgorithm::Trapezoidal: {
3193 this->townsendTrackTrapezoidal(-voltage);
3194
3195 break;
3196 }
3197 default: {
3198 MayDay::Error("DischargeInceptionStepper::computeTownsendCriterionStationary -- logic bust");
3199
3200 break;
3201 }
3202 }
3203
3204 m_tracerParticleSolver->deposit(townsendCriterionMinu);
3205
3206 m_amr->conservativeAverage(townsendCriterionMinu, m_realm, m_phase);
3207 m_amr->interpGhost(townsendCriterionMinu, m_realm, m_phase);
3208
3209 // For turning K into exp(K) - 1
3210 auto exponentiate = [](const Real x) -> Real {
3211 return x > 0.0 ? exp(x) - 1 : 0.0;
3212 };
3213
3214 DataOps::copy(expKPlus, KPlus);
3215 DataOps::copy(expKMinu, KMinu);
3216
3217 DataOps::compute(expKPlus, exponentiate, m_amr->getMultiCutVofIterator(m_realm, m_phase));
3218 DataOps::compute(expKMinu, exponentiate, m_amr->getMultiCutVofIterator(m_realm, m_phase));
3219
3220 DataOps::multiply(townsendCriterionPlus, expKPlus);
3221 DataOps::multiply(townsendCriterionMinu, expKMinu);
3222
3223 this->getMaxValueAndLocation(maxTPlus, maxTPlusPos, townsendCriterionPlus);
3224 this->getMaxValueAndLocation(maxTMinu, maxTMinuPos, townsendCriterionMinu);
3225
3226 // If we're not doing full integration we truncate the Townsend value to 1.
3227 if (!m_fullIntegration) {
3228 auto truncate = [](const Real x) -> Real {
3229 return std::min(x, 1.0);
3230 };
3231
3232 DataOps::compute(townsendCriterionPlus, truncate, m_amr->getMultiCutVofIterator(m_realm, m_phase));
3233 DataOps::compute(townsendCriterionMinu, truncate, m_amr->getMultiCutVofIterator(m_realm, m_phase));
3234 }
3235 }
3236
3237 m_townsendCriterionPlus.push_back(townsendCriterionPlus);
3238 m_townsendCriterionMinu.push_back(townsendCriterionMinu);
3239
3240 m_TPlusValues.emplace_back(std::make_tuple(voltage, maxTPlus, maxTPlusPos));
3241 m_TMinuValues.emplace_back(std::make_tuple(voltage, maxTMinu, maxTMinuPos));
3242 }
3243}
3244
3245template <typename P, typename F, typename C>
3246void
3248{
3249 CH_TIME("DischargeInceptionStepper::computeTownsendCriterionTransient");
3250 if (m_verbosity > 5) {
3251 pout() << "DischargeInceptionStepper::computeTownsendCriterionTransient" << endl;
3252 }
3253
3254 // Compute T-value on each particle using specified algorithm.
3255 switch (m_inceptionAlgorithm) {
3256 case IntegrationAlgorithm::Euler: {
3257 this->townsendTrackEuler(a_voltage);
3258
3259 break;
3260 }
3261 case IntegrationAlgorithm::Trapezoidal: {
3262 this->townsendTrackTrapezoidal(a_voltage);
3263
3264 break;
3265 }
3266 default: {
3267 MayDay::Error("DischargeInceptionStepper::computeTownsendCriterionTransient - logic bust");
3268
3269 break;
3270 }
3271 }
3272
3273 // Compute gamma
3274 EBAMRCellData gamma;
3275 m_amr->allocate(gamma, m_realm, m_phase, 1);
3276 DataOps::setValue(gamma, 0.0);
3277 m_tracerParticleSolver->deposit(gamma);
3278
3279 // Turn K into exp(K)-1
3280 auto exponentiate = [](const Real x) -> Real {
3281 return x > 0.0 ? exp(x) - 1 : 0.0;
3282 };
3283 DataOps::copy(m_townsendCriterion, m_inceptionIntegral);
3284 DataOps::compute(m_townsendCriterion, exponentiate, m_amr->getMultiCutVofIterator(m_realm, m_phase));
3285 DataOps::multiply(m_townsendCriterion, gamma);
3286
3287 // If we're running without full integration we truncate the Townsend value to 1.
3288 if (!m_fullIntegration) {
3290 m_townsendCriterion,
3291 [](const Real x) {
3292 return std::min(x, 1.0);
3293 },
3294 m_amr->getMultiCutVofIterator(m_realm, m_phase));
3295 }
3296
3297 m_amr->conservativeAverage(m_townsendCriterion, m_realm, m_phase);
3298 m_amr->interpGhost(m_townsendCriterion, m_realm, m_phase);
3299}
3300
3301template <typename P, typename F, typename C>
3302void
3304{
3305 CH_TIME("DischargeInceptionStepper::townsendTrackEuler");
3306 if (m_verbosity > 5) {
3307 pout() << "DischargeInceptionStepper::townsendTrackEuler" << endl;
3308 }
3309 const RealVect probLo = m_amr->getProbLo();
3310 const RealVect probHi = m_amr->getProbHi();
3311
3312 // Allocate a data holder for holding the processed particles. This
3313 // will be faster because then we only have to iterate through the
3314 // particles that are actually still moving.
3315 ParticleContainer<P> amrProcessedParticles;
3316 m_amr->allocate(amrProcessedParticles, m_realm);
3317
3318 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
3319
3320 m_tracerParticleSolver->remap();
3321
3322 size_t particlesBefore = 0;
3323
3324 if (m_debug) {
3325 particlesBefore = amrParticles.getNumberOfValidParticlesGlobal();
3326 }
3327
3328 // Allocate something that holds the velocity of the ions
3329 EBAMRCellData scratch;
3330 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
3331 this->superposition(scratch, a_voltage);
3332
3333 m_tracerParticleSolver->setVelocity(scratch);
3334 m_tracerParticleSolver->interpolateVelocities();
3335
3336 while (amrParticles.getNumberOfValidParticlesGlobal() > 0) {
3337
3338 // Euler integration.
3339 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3340 const Real dx = m_amr->getDx()[lvl];
3341
3342 // Integrate particles until they leave alpha > 0 or strike a cathode surface.
3343 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3344 const DataIterator& dit = dbl.dataIterator();
3345
3346 const int nbox = dit.size();
3347
3348#pragma omp parallel for schedule(runtime)
3349 for (int mybox = 0; mybox < nbox; mybox++) {
3350 const DataIndex& din = dit[mybox];
3351
3352 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
3353 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
3354
3355 double* const w = solverLeaf.weightColumn();
3356 double* const pos[SpaceDim] = {
3357 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
3358 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
3359 solverLeaf.template column<&P::v_y>(),
3360 solverLeaf.template column<&P::v_z>())};
3361
3362 std::size_t i = 0;
3363 while (i < solverLeaf.size()) {
3364 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
3365 const RealVect vec(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
3366 const Real v = vec.vectorLength();
3367 const Real E = v;
3368 const Real deltaX = m_townsendGridDx * dx;
3369 const Real dt = deltaX / v;
3370 const RealVect newPos = x + dt * vec;
3371
3372 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
3373 const bool insideEB = this->particleInsideEB(newPos);
3374
3375 // Stop integration if avalanche phase is over
3376 const Real alpha = m_alpha(E, x);
3377 const Real eta = m_eta(E, x);
3378 const Real alphaEff = alpha - eta;
3379 const bool negativeAlpha = alphaEff <= 0.0;
3380
3381 w[i] = 0.0;
3382
3383 if (insideEB) {
3384 w[i] = m_secondaryEmission(E, x);
3385
3386 processedLeaf.appendParticle(solverLeaf, i);
3387 solverLeaf.remove(i);
3388 }
3389 else if (outsideDomain || negativeAlpha) {
3390 processedLeaf.appendParticle(solverLeaf, i);
3391 solverLeaf.remove(i);
3392 }
3393 else {
3394 for (int dir = 0; dir < SpaceDim; dir++) {
3395 pos[dir][i] = newPos[dir];
3396 }
3397
3398 i++;
3399 }
3400 }
3401 }
3402 }
3403
3404 // Update velocities.
3405 m_tracerParticleSolver->remap();
3406 m_tracerParticleSolver->interpolateVelocities();
3407 }
3408
3409 ParticleOps::copyDestructive(amrParticles, amrProcessedParticles);
3410
3411 this->rewindTracerParticles();
3412
3413 size_t particlesAfter = 0;
3414
3415 if (m_debug) {
3416 particlesAfter = amrParticles.getNumberOfValidParticlesGlobal();
3417 }
3418
3419 if (particlesBefore != particlesAfter) {
3420 MayDay::Warning("DischargeInceptionStepper::townsendTrackEuler - lost/gained particles!");
3421 }
3422}
3423
3424template <typename P, typename F, typename C>
3425void
3427{
3428 CH_TIME("DischargeInceptionStepper::townsendTrackTrapezoidal");
3429 if (m_verbosity > 5) {
3430 pout() << "DischargeInceptionStepper::townsendTrackTrapezoidal" << endl;
3431 }
3432
3433 // TLDR: We move the particle using Heun's method.
3434 //
3435 // p^(k+1) = p^k + 0.5 * dt * [v(p^k) + v(p^l)]
3436 //
3437 // where p^l = p^k + dt * v(p^k). We will set dt = d/|v(p^k)|.
3438
3439 const RealVect probLo = m_amr->getProbLo();
3440 const RealVect probHi = m_amr->getProbHi();
3441
3442 // Allocate a data holder for holding the processed particles. This
3443 // will be faster because then we only have to iterate through the
3444 // particles that are actually still moving.
3445 ParticleContainer<P> amrProcessedParticles;
3446 m_amr->allocate(amrProcessedParticles, m_realm);
3447
3448 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
3449
3450 m_tracerParticleSolver->remap();
3451
3452 size_t particlesBefore = 0;
3453
3454 if (m_debug) {
3455 particlesBefore = amrParticles.getNumberOfValidParticlesGlobal();
3456 }
3457
3458 // Allocate something that holds the velocity of the ions
3459 EBAMRCellData scratch;
3460 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
3461 this->superposition(scratch, a_voltage);
3462
3463 m_tracerParticleSolver->setVelocity(scratch);
3464 m_tracerParticleSolver->interpolateVelocities();
3465
3466 while (amrParticles.getNumberOfValidParticlesGlobal() > 0) {
3467
3468 // Euler stage.
3469 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3470 const Real dx = m_amr->getDx()[lvl];
3471
3472 // First stage
3473 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3474 const DataIterator& dit = dbl.dataIterator();
3475
3476 const int nbox = dit.size();
3477
3478#pragma omp parallel for schedule(runtime)
3479 for (int mybox = 0; mybox < nbox; mybox++) {
3480 const DataIndex& din = dit[mybox];
3481
3482 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
3483 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
3484
3485 double* const w = solverLeaf.weightColumn();
3486 double* const pos[SpaceDim] = {
3487 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
3488 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
3489 solverLeaf.template column<&P::v_y>(),
3490 solverLeaf.template column<&P::v_z>())};
3491 ParticleReal* const vk[SpaceDim] = {D_DECL(solverLeaf.template column<&P::vk_x>(),
3492 solverLeaf.template column<&P::vk_y>(),
3493 solverLeaf.template column<&P::vk_z>())};
3494 ParticleReal* const alphaEffCol = solverLeaf.template column<&P::alphaEff>();
3495 ParticleReal* const dtCol = solverLeaf.template column<&P::dtStep>();
3496
3497 std::size_t i = 0;
3498 while (i < solverLeaf.size()) {
3499 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
3500 const RealVect vec(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
3501 const Real v = vec.vectorLength();
3502 const Real E = v;
3503 const Real alpha = m_alpha(E, x);
3504 const Real eta = m_eta(E, x);
3505 const Real alphaEff = alpha - eta;
3506
3507 // Compute a time step that we will use for the integration.
3508 const Real deltaX = m_townsendGridDx * dx;
3509 const Real dt = deltaX / v;
3510 const RealVect newPos = x + vec * dt;
3511
3512 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
3513 const bool insideEB = this->particleInsideEB(newPos);
3514
3515 if (insideEB) {
3516 w[i] = m_secondaryEmission(E, x);
3517
3518 processedLeaf.appendParticle(solverLeaf, i);
3519 solverLeaf.remove(i);
3520 }
3521 else if (alphaEff < 0.0) {
3522 processedLeaf.appendParticle(solverLeaf, i);
3523 solverLeaf.remove(i);
3524 }
3525 else if (outsideDomain) {
3526 processedLeaf.appendParticle(solverLeaf, i);
3527 solverLeaf.remove(i);
3528 }
3529 else {
3530 // Do an Euler step, storing alpha(p^k), v(p^k), and the time step size.
3531 alphaEffCol[i] = alphaEff;
3532 dtCol[i] = dt;
3533 for (int dir = 0; dir < SpaceDim; dir++) {
3534 vk[dir][i] = vec[dir];
3535 pos[dir][i] = newPos[dir];
3536 }
3537
3538 i++;
3539 }
3540 }
3541 }
3542 }
3543
3544 // Remap and update velocities.
3545 m_tracerParticleSolver->remap();
3546 m_tracerParticleSolver->interpolateVelocities();
3547
3548 // Second stage.
3549 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3550 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3551 const DataIterator& dit = dbl.dataIterator();
3552
3553 const int nbox = dit.size();
3554
3555#pragma omp parallel for schedule(runtime)
3556 for (int mybox = 0; mybox < nbox; mybox++) {
3557 const DataIndex& din = dit[mybox];
3558
3559 ParticleSoA<P>& solverLeaf = amrParticles[lvl][din];
3560 ParticleSoA<P>& processedLeaf = amrProcessedParticles[lvl][din];
3561
3562 double* const w = solverLeaf.weightColumn();
3563 double* const pos[SpaceDim] = {
3564 D_DECL(solverLeaf.positionColumn(0), solverLeaf.positionColumn(1), solverLeaf.positionColumn(2))};
3565 const ParticleReal* const vel[SpaceDim] = {D_DECL(solverLeaf.template column<&P::v_x>(),
3566 solverLeaf.template column<&P::v_y>(),
3567 solverLeaf.template column<&P::v_z>())};
3568 const ParticleReal* const vk[SpaceDim] = {D_DECL(solverLeaf.template column<&P::vk_x>(),
3569 solverLeaf.template column<&P::vk_y>(),
3570 solverLeaf.template column<&P::vk_z>())};
3571 const ParticleReal* const alphaEffCol = solverLeaf.template column<&P::alphaEff>();
3572 const ParticleReal* const dtCol = solverLeaf.template column<&P::dtStep>();
3573
3574 std::size_t i = 0;
3575 while (i < solverLeaf.size()) {
3576 const Real dt = dtCol[i];
3577 const RealVect vkv(D_DECL(vk[0][i], vk[1][i], vk[2][i]));
3578 const RealVect vk1(D_DECL(vel[0][i], vel[1][i], vel[2][i]));
3579 const RealVect x(D_DECL(pos[0][i], pos[1][i], pos[2][i]));
3580 const Real E = vk1.vectorLength();
3581
3582 // Note the weird subtraction since the position was updated to p^k + dt * v^k.
3583 const RealVect oldPos = x - dt * vkv;
3584 const RealVect newPos = x + 0.5 * dt * (vk1 - vkv);
3585
3586 // Compute new alpha.
3587 const Real alphak = alphaEffCol[i];
3588 const Real alphak1 = m_alpha(E, x) - m_eta(E, x);
3589
3590 // Stop integration for particles that move into regions alpha < 0.0,
3591 // inside the EB or outside of the domain.
3592 const bool negativeAlpha = (alphak + alphak1) < 0.0;
3593 const bool outsideDomain = this->particleOutsideGrid(newPos, probLo, probHi);
3594 const bool insideEB = this->particleInsideEB(newPos);
3595
3596 // (negativeAlpha is computed but, as in the original, only outsideDomain/insideEB stop the track.)
3597 (void)negativeAlpha;
3598
3599 if (insideEB) {
3600 w[i] = m_secondaryEmission(E, oldPos);
3601
3602 processedLeaf.appendParticle(solverLeaf, i);
3603 solverLeaf.remove(i);
3604 }
3605 else if (outsideDomain) {
3606 processedLeaf.appendParticle(solverLeaf, i);
3607 solverLeaf.remove(i);
3608 }
3609 else {
3610 for (int dir = 0; dir < SpaceDim; dir++) {
3611 pos[dir][i] = newPos[dir];
3612 }
3613
3614 i++;
3615 }
3616 }
3617 }
3618 }
3619
3620 // Remap and update velocities.
3621 m_tracerParticleSolver->remap();
3622 m_tracerParticleSolver->interpolateVelocities();
3623 }
3624
3625 // Copy processed particles over to the solver particles.
3626 ParticleOps::copyDestructive(amrParticles, amrProcessedParticles);
3627
3628 this->rewindTracerParticles();
3629
3630 size_t particlesAfter = 0;
3631
3632 if (m_debug) {
3633 particlesAfter = amrParticles.getNumberOfValidParticlesGlobal();
3634 }
3635
3636 if (particlesBefore != particlesAfter) {
3637 MayDay::Warning("DischargeInceptionStepper::townsendTrackTrapezoidal - lost/gained particles!");
3638 }
3639}
3640
3641template <typename P, typename F, typename C>
3642Real
3643DischargeInceptionStepper<P, F, C>::computeRdot(const Real& a_voltage) const noexcept
3644{
3645 CH_TIME("DischargeInceptionStepper::computeRdot");
3646 if (m_verbosity > 5) {
3647 pout() << "DischargeInceptionStepper::computeRdot" << endl;
3648 }
3649
3650 Real Rdot = 0.0;
3651
3652 // TLDR: We are computing the integral of done/dt * (1-eta/alpha) over the critical volume and integral(j/e * dS) over
3653 // the "critical surface"
3654 const RealVect probLo = m_amr->getProbLo();
3655
3656 // Compute the electric field at the input voltage
3657 EBAMRCellData scratch;
3658 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
3659 this->superposition(scratch, a_voltage);
3660
3661 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3662 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3663 const DataIterator& dit = dbl.dataIterator();
3664 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
3665 const Real dx = m_amr->getDx()[lvl];
3666
3667 const Real vol = std::pow(dx, SpaceDim);
3668 const Real area = std::pow(dx, SpaceDim - 1);
3669
3670 const int nbox = dit.size();
3671
3672#pragma omp parallel for schedule(runtime) reduction(+ : Rdot)
3673 for (int mybox = 0; mybox < nbox; mybox++) {
3674 const DataIndex& din = dit[mybox];
3675
3676 const EBISBox& ebisbox = ebisl[din];
3677 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_realm)[lvl])[din];
3678
3679 const EBCellFAB& electricField = (*scratch[lvl])[din];
3680 const EBCellFAB& inceptionIntegral = (*m_inceptionIntegral[lvl])[din];
3681 const EBCellFAB& fieldEmission = (*m_emissionRate[lvl])[din];
3682 const EBCellFAB& ionDensity = (*m_ionSolver->getPhi()[lvl])[din];
3683
3684 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
3685 const FArrayBox& inceptionIntegralReg = inceptionIntegral.getFArrayBox();
3686 const FArrayBox& ionDensityReg = ionDensity.getFArrayBox();
3687
3688 // Add contribution from background ionization in regular cells.
3689 auto regularKernel = [&](const IntVect& iv) -> void {
3690 if (ebisbox.isRegular(iv) && validCells(iv, 0)) {
3691 if (inceptionIntegralReg(iv, 0) >= m_inceptionK) {
3692 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv)) * dx;
3693 const RealVect EE = RealVect(
3694 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
3695 const Real E = EE.vectorLength();
3696
3697 const Real alpha = m_alpha(E, pos);
3698 const Real eta = m_eta(E, pos);
3699 const Real k = m_detachmentRate(E, pos);
3700 const Real dndt = m_backgroundRate(E, pos) + k * ionDensityReg(iv, 0);
3701
3702 CH_assert(alpha >= eta);
3703
3704 Rdot += dndt * (1.0 - eta / alpha) * vol;
3705 }
3706 }
3707 };
3708
3709 // Add contribution from background ionization and field emission in cut-cells.
3710 auto irregularKernel = [&](const VolIndex& vof) -> void {
3711 const IntVect iv = vof.gridIndex();
3712 if (ebisbox.isIrregular(iv) && validCells(iv, 0)) {
3713 if (inceptionIntegral(vof, 0) >= m_inceptionK) {
3714 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
3715 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
3716 const Real E = EE.vectorLength();
3717
3718 const Real kappa = ebisbox.volFrac(vof);
3719 const Real areaFrac = ebisbox.bndryArea(vof);
3720 const Real alpha = m_alpha(E, pos);
3721 const Real eta = m_eta(E, pos);
3722 const Real k = m_detachmentRate(E, pos);
3723 const Real j = m_fieldEmission(E, pos);
3724 const Real dndt = m_backgroundRate(E, pos) + k * ionDensity(vof, 0);
3725
3726 Rdot += dndt * (1.0 - eta / alpha) * kappa * vol;
3727 Rdot += j / Units::Qe * areaFrac * area;
3728 }
3729 }
3730 };
3731
3732 // Run the kernels. Not vectorizable: FP sum reduction (Rdot) + multiple std::function coefficients
3733 // (m_alpha/m_eta/m_detachmentRate/m_fieldEmission/m_backgroundRate) + data-dependent branch.
3734 Box cellBox = dbl[din];
3735 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
3736
3737 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
3738 BoxLoops::loop(vofit, irregularKernel);
3739 }
3740 }
3741
3742 return ParallelOps::sum(Rdot);
3743}
3744
3745template <typename P, typename F, typename C>
3746void
3748{
3749 CH_TIME("DischargeInceptionStepper::rewindTracerParticles");
3750 if (m_verbosity > 5) {
3751 pout() << "DischargeInceptionStepper::rewindTracerParticles" << endl;
3752 }
3753
3754 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
3755
3756 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3757 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3758 const DataIterator& dit = dbl.dataIterator();
3759
3760 const int nbox = dit.size();
3761
3762#pragma omp parallel for schedule(runtime)
3763 for (int mybox = 0; mybox < nbox; mybox++) {
3764 const DataIndex& din = dit[mybox];
3765
3766 ParticleSoA<P>& leaf = amrParticles[lvl][din];
3767
3768 double* const pos[SpaceDim] = {D_DECL(leaf.positionColumn(0), leaf.positionColumn(1), leaf.positionColumn(2))};
3769 const double* const x0[SpaceDim] = {
3770 D_DECL(leaf.template column<&P::x0_x>(), leaf.template column<&P::x0_y>(), leaf.template column<&P::x0_z>())};
3771
3772 ParticleLoops::loop(leaf, [&](const std::size_t i) {
3773 for (int dir = 0; dir < SpaceDim; dir++) {
3774 pos[dir][i] = x0[dir][i]; // rewind to the snapshot position
3775 }
3776 });
3777 }
3778 }
3779
3780 amrParticles.remap();
3781}
3782
3783template <typename P, typename F, typename C>
3784void
3786{
3787 CH_TIME("DischargeInceptionStepper::resetTracerParticles");
3788 if (m_verbosity > 5) {
3789 pout() << "DischargeInceptionStepper::resetTracerParticles" << endl;
3790 }
3791
3792 ParticleContainer<P>& amrParticles = m_tracerParticleSolver->getParticles();
3793
3794 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3795 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3796 const DataIterator& dit = dbl.dataIterator();
3797
3798 const int nbox = dit.size();
3799
3800#pragma omp parallel for schedule(runtime)
3801 for (int mybox = 0; mybox < nbox; mybox++) {
3802 const DataIndex& din = dit[mybox];
3803
3804 ParticleSoA<P>& leaf = amrParticles[lvl][din];
3805
3806 double* const w = leaf.weightColumn();
3807
3808 ParticleLoops::loop(leaf, [&](const std::size_t i) {
3809 w[i] = 0.0;
3810 });
3811 }
3812 }
3813}
3814
3815template <typename P, typename F, typename C>
3816void
3818{
3819 CH_TIME("DischargeInceptionStepper::computeBackgroundIonizationStationary");
3820 if (m_verbosity > 5) {
3821 pout() << "DischargeInceptionStepper::computeBackgroundIonizationStationary" << endl;
3822 }
3823
3824 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
3825 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
3826 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
3827
3828 m_backgroundIonizationStationary.resize(0);
3829
3830 // Holds the electric field
3831 EBAMRCellData scratch;
3832 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
3833
3834 // Solve ionization volume for each voltage
3835 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
3836 const Real voltage = m_voltageSweeps[i];
3837
3838 // Compute the electric field into the scratch storage
3839 this->superposition(scratch, voltage);
3840
3841 EBAMRCellData backgroundRate;
3842
3843 m_amr->allocate(backgroundRate, m_realm, m_phase, 1);
3844
3845 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3846 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3847 const DataIterator& dit = dbl.dataIterator();
3848 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
3849 const Real dx = m_amr->getDx()[lvl];
3850 const RealVect probLo = m_amr->getProbLo();
3851
3852 const int nbox = dit.size();
3853
3854#pragma omp parallel for schedule(runtime)
3855 for (int mybox = 0; mybox < nbox; mybox++) {
3856 const DataIndex& din = dit[mybox];
3857
3858 const EBISBox& ebisbox = ebisl[din];
3859
3860 EBCellFAB& bgIonization = (*backgroundRate[lvl])[din];
3861 FArrayBox& bgIonizationReg = bgIonization.getFArrayBox();
3862
3863 const EBCellFAB& electricField = (*scratch[lvl])[din];
3864 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
3865
3866 auto regularKernel = [&](const IntVect& iv) -> void {
3867 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv)) * dx;
3868 const RealVect EE = RealVect(
3869 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
3870 const Real E = EE.vectorLength();
3871
3872 bgIonizationReg(iv, 0) = m_backgroundRate(E, pos);
3873 };
3874
3875 auto irregularKernel = [&](const VolIndex& vof) -> void {
3876 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
3877 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
3878 const Real E = EE.vectorLength();
3879
3880 bgIonization(vof, 0) = m_backgroundRate(E, pos);
3881 };
3882
3883 // Execute kernels. Not vectorizable: m_backgroundRate std::function coefficient evaluated per cell.
3884 // (Representative of the stationary coefficient computes -- detachment/field-emission/evaluateFunction/
3885 // ion mobility/ion diffusion all share this std::function-per-cell structure.)
3886 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
3887
3888 BoxLoops::loop<D_DECL(1, 1, 1)>(dbl[din], regularKernel);
3889 BoxLoops::loop(vofit, irregularKernel);
3890 }
3891 }
3892
3893 m_backgroundIonizationStationary.push_back(backgroundRate);
3894 }
3895}
3896
3897template <typename P, typename F, typename C>
3898void
3900{
3901 CH_TIME("DischargeInceptionStepper::computeDetachmentStationary");
3902 if (m_verbosity > 5) {
3903 pout() << "DischargeInceptionStepper::computeDetachmentStationary" << endl;
3904 }
3905
3906 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
3907 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
3908 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
3909
3910 m_detachmentStationary.resize(0);
3911
3912 // Holds the electric field
3913 EBAMRCellData scratch;
3914 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
3915
3916 // Solve ionization volume for each voltage
3917 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
3918 const Real voltage = m_voltageSweeps[i];
3919
3920 // Compute the electric field into the scratch storage
3921 this->superposition(scratch, voltage);
3922
3923 EBAMRCellData detachmentRate;
3924
3925 m_amr->allocate(detachmentRate, m_realm, m_phase, 1);
3926
3927 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
3928 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
3929 const DataIterator& dit = dbl.dataIterator();
3930 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
3931 const Real dx = m_amr->getDx()[lvl];
3932 const RealVect probLo = m_amr->getProbLo();
3933
3934 const int nbox = dit.size();
3935
3936#pragma omp parallel for schedule(runtime)
3937 for (int mybox = 0; mybox < nbox; mybox++) {
3938 const DataIndex& din = dit[mybox];
3939
3940 const EBISBox& ebisbox = ebisl[din];
3941
3942 EBCellFAB& detachment = (*detachmentRate[lvl])[din];
3943 FArrayBox& detachmentReg = detachment.getFArrayBox();
3944
3945 const EBCellFAB& ionDensity = (*m_ionSolver->getPhi()[lvl])[din];
3946 const EBCellFAB& electricField = (*scratch[lvl])[din];
3947
3948 const FArrayBox& ionDensityReg = ionDensity.getFArrayBox();
3949 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
3950
3951 auto regularKernel = [&](const IntVect& iv) -> void {
3952 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv)) * dx;
3953 const RealVect EE = RealVect(
3954 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
3955 const Real E = EE.vectorLength();
3956 const Real phi = ionDensityReg(iv, 0);
3957
3958 detachmentReg(iv, 0) = m_detachmentRate(E, pos) * phi;
3959 };
3960
3961 auto irregularKernel = [&](const VolIndex& vof) -> void {
3962 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
3963 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
3964 const Real E = EE.vectorLength();
3965 const Real phi = ionDensity(vof, 0);
3966
3967 detachment(vof, 0) = m_detachmentRate(E, pos) * phi;
3968 };
3969
3970 // Execute kernels
3971 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
3972
3973 BoxLoops::loop<D_DECL(1, 1, 1)>(dbl[din], regularKernel);
3974 BoxLoops::loop(vofit, irregularKernel);
3975 }
3976 }
3977
3978 m_amr->conservativeAverage(detachmentRate, m_realm, m_phase);
3979 m_amr->interpGhost(detachmentRate, m_realm, m_phase);
3980
3981 m_detachmentStationary.push_back(detachmentRate);
3982 }
3983}
3984
3985template <typename P, typename F, typename C>
3986void
3988{
3989 CH_TIME("DischargeInceptionStepper::computeFieldEmissionStationary");
3990 if (m_verbosity > 5) {
3991 pout() << "DischargeInceptionStepper::computeFieldEmissionStationary" << endl;
3992 }
3993
3994 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
3995 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
3996 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
3997
3998 m_emissionRatesPlus.resize(0);
3999 m_emissionRatesMinu.resize(0);
4000
4001 const int numVoltages = m_inceptionIntegralPlus.size();
4002
4003 // Holds the electric field
4004 EBAMRCellData scratchPlus;
4005 EBAMRCellData scratchMinu;
4006
4007 m_amr->allocate(scratchPlus, m_realm, m_phase, SpaceDim);
4008 m_amr->allocate(scratchMinu, m_realm, m_phase, SpaceDim);
4009
4010 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
4011 const Real voltage = m_voltageSweeps[i];
4012
4013 // Compute the electric field into the scratch storage
4014 this->superposition(scratchPlus, +voltage);
4015 this->superposition(scratchMinu, -voltage);
4016
4017 EBAMRCellData emissionRatesPlus;
4018 EBAMRCellData emissionRatesMinu;
4019
4020 m_amr->allocate(emissionRatesPlus, m_realm, m_phase, 1);
4021 m_amr->allocate(emissionRatesMinu, m_realm, m_phase, 1);
4022
4023 DataOps::setValue(emissionRatesPlus, 0.0);
4024 DataOps::setValue(emissionRatesMinu, 0.0);
4025
4026 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4027 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4028 const DataIterator& dit = dbl.dataIterator();
4029 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4030 const Real& dx = m_amr->getDx()[lvl];
4031 const RealVect probLo = m_amr->getProbLo();
4032
4033 const int nbox = dit.size();
4034
4035#pragma omp parallel for schedule(runtime)
4036 for (int mybox = 0; mybox < nbox; mybox++) {
4037 const DataIndex& din = dit[mybox];
4038
4039 const EBISBox& ebisbox = ebisl[din];
4040
4041 // Here, emissionPlus is when we have the standard voltage (V = 1) and
4042 // emissionMinu is when the voltage is reverted.
4043 EBCellFAB& emissionPlus = (*emissionRatesPlus[lvl])[din];
4044 EBCellFAB& emissionMinu = (*emissionRatesMinu[lvl])[din];
4045
4046 emissionPlus.setVal(0.0);
4047 emissionMinu.setVal(0.0);
4048
4049 const EBCellFAB& electricFieldPlus = (*scratchPlus[lvl])[din];
4050 const EBCellFAB& electricFieldMinu = (*scratchMinu[lvl])[din];
4051
4052 auto irregularKernel = [&](const VolIndex& vof) -> void {
4053 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
4054
4055 const RealVect Eplus = RealVect(
4056 D_DECL(electricFieldPlus(vof, 0), electricFieldPlus(vof, 1), electricFieldPlus(vof, 2)));
4057 const RealVect Eminu = RealVect(
4058 D_DECL(electricFieldMinu(vof, 0), electricFieldMinu(vof, 1), electricFieldMinu(vof, 2)));
4059
4060 const Real normalEplus = Eplus.dotProduct(ebisbox.normal(vof));
4061 const Real normalEminu = Eminu.dotProduct(ebisbox.normal(vof));
4062
4063 emissionPlus(vof, 0) = 0.0;
4064 emissionMinu(vof, 0) = 0.0;
4065
4066 if (normalEplus < 0.0) {
4067 emissionPlus(vof, 0) = m_fieldEmission(Eplus.vectorLength(), pos);
4068 }
4069 if (normalEminu < 0.0) {
4070 emissionMinu(vof, 0) = m_fieldEmission(Eminu.vectorLength(), pos);
4071 }
4072 };
4073
4074 // Execute kernels
4075 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4076 BoxLoops::loop(vofit, irregularKernel);
4077 }
4078 }
4079
4080 m_emissionRatesPlus.push_back(emissionRatesPlus);
4081 m_emissionRatesMinu.push_back(emissionRatesMinu);
4082 }
4083}
4084
4085template <typename P, typename F, typename C>
4086void
4088 const Real& a_voltage) const noexcept
4089{
4090 CH_TIME("DischargeInceptionStepper::computeFieldEmission");
4091 if (m_verbosity > 5) {
4092 pout() << "DischargeInceptionStepper::computeFieldEmission" << endl;
4093 }
4094
4095 CH_assert(a_emissionRate[0]->nComp() == 1);
4096
4097 const RealVect probLo = m_amr->getProbLo();
4098
4099 // Compute the electric field into the scratch storage
4100 EBAMRCellData scratch;
4101 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
4102 this->superposition(scratch, a_voltage);
4103
4104 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4105 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4106 const DataIterator& dit = dbl.dataIterator();
4107 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4108 const Real dx = m_amr->getDx()[lvl];
4109
4110 const int nbox = dit.size();
4111
4112#pragma omp parallel for schedule(runtime)
4113 for (int mybox = 0; mybox < nbox; mybox++) {
4114 const DataIndex& din = dit[mybox];
4115
4116 const EBISBox& ebisbox = ebisl[din];
4117
4118 // Here, emissionPlus is when we have the standard voltage (V = 1) and
4119 // emissionMinu is when the voltage is reverted.
4120 EBCellFAB& emission = (*a_emissionRate[lvl])[din];
4121
4122 emission.setVal(0.0);
4123
4124 const EBCellFAB& electricField = (*scratch[lvl])[din];
4125 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
4126
4127 auto irregularKernel = [&](const VolIndex& vof) -> void {
4128 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
4129 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
4130 const Real E = EE.vectorLength();
4131
4132 if (EE.dotProduct(ebisbox.normal(vof)) > 0.0) {
4133 emission(vof, 0) = m_fieldEmission(E, pos);
4134 }
4135 };
4136
4137 // Execute kernels
4138 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4139 BoxLoops::loop(vofit, irregularKernel);
4140 }
4141 }
4142}
4143
4144template <typename P, typename F, typename C>
4145void
4147 EBAMRCellData& a_data,
4148 const Real& a_voltage,
4149 const std::function<Real(const Real E, const RealVect x)>& a_func) const noexcept
4150{
4151 CH_TIME("DischargeInceptionStepper::evaluateFunction");
4152 if (m_verbosity > 5) {
4153 pout() << "DischargeInceptionStepper::evaluateFunction" << endl;
4154 }
4155
4156 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4157 this->evaluateFunction(*a_data[lvl], a_voltage, a_func, lvl);
4158 }
4159}
4160
4161template <typename P, typename F, typename C>
4162void
4164 const Real& a_voltage,
4165 const std::function<Real(const Real E, const RealVect x)>& a_func,
4166 const int a_level) const noexcept
4167{
4168 CH_TIME("DischargeInceptionStepper::evaluateFunction(level)");
4169 if (m_verbosity > 5) {
4170 pout() << "DischargeInceptionStepper::evaluateFunction(level)" << endl;
4171 }
4172
4173 CH_assert(a_level >= 0);
4174 CH_assert(a_level <= m_amr->getFinestLevel());
4175
4176 // Compute the electric field
4177 EBAMRCellData scratch;
4178 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
4179 this->superposition(scratch, a_voltage);
4180
4181 const RealVect probLo = m_amr->getProbLo();
4182
4183 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[a_level];
4184 const DataIterator& dit = dbl.dataIterator();
4185 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[a_level];
4186 const Real dx = m_amr->getDx()[a_level];
4187
4188 const int nbox = dit.size();
4189
4190#pragma omp parallel for schedule(runtime)
4191 for (int mybox = 0; mybox < nbox; mybox++) {
4192 const DataIndex& din = dit[mybox];
4193
4194 const EBISBox& ebisbox = ebisl[din];
4195
4196 // Here, emissionPlus is when we have the standard voltage (V = 1) and
4197 // emissionMinu is when the voltage is reverted.
4198 EBCellFAB& data = a_data[din];
4199 FArrayBox& dataReg = data.getFArrayBox();
4200
4201 data.setVal(0.0);
4202
4203 const EBCellFAB& electricField = (*scratch[a_level])[din];
4204 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
4205
4206 auto regularKernel = [&](const IntVect& iv) -> void {
4207 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv)) * dx;
4208 const RealVect EE = RealVect(D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
4209 const Real E = EE.vectorLength();
4210
4211 dataReg(iv, 0) = a_func(E, pos);
4212 };
4213
4214 auto irregularKernel = [&](const VolIndex& vof) -> void {
4215 const RealVect pos = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
4216 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
4217 const Real E = EE.vectorLength();
4218
4219 data(vof, 0) = a_func(E, pos);
4220 };
4221
4222 // Execute kernels
4223 Box cellBox = dbl[din];
4224 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[a_level])[din];
4225
4226 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4227 BoxLoops::loop(vofit, irregularKernel);
4228 }
4229}
4230
4231template <typename P, typename F, typename C>
4232void
4234{
4235 CH_TIME("DischargeInceptionStepper::computeInceptionVoltageVolume");
4236 if (m_verbosity > 5) {
4237 pout() << "DischargeInceptionStepper::computeInceptionVoltageVolume" << endl;
4238 }
4239
4240 // TLDR: This routine runs through all the K-values in each cell and estimates the
4241 // inception voltage using linear interpolation.
4242
4243 CH_assert(m_mode == Mode::Stationary);
4244
4245 if (m_inceptionIntegralPlus.size() < 2) {
4246 DataOps::setValue(m_inceptionVoltagePlus, std::numeric_limits<Real>::quiet_NaN());
4247 DataOps::setValue(m_inceptionVoltageMinu, std::numeric_limits<Real>::quiet_NaN());
4248
4249 MayDay::Warning("DischargeInceptionStepper::computeInceptionVoltageVolume -- not enough voltages for estimating "
4250 "inception voltage");
4251 }
4252 else {
4253 constexpr int comp = 0;
4254
4255 // Function which interpolates the inception voltage if possible. Used in the kernels. This one does interpolation.
4256 auto calcUincInterp = [Kinc = this->m_inceptionK,
4257 &V = this->m_voltageSweeps](const std::vector<Real>& K,
4258 const std::vector<Real>& T) -> std::array<Real, 3> {
4259 Real streamerInc = std::numeric_limits<Real>::quiet_NaN();
4260 Real townsendInc = std::numeric_limits<Real>::quiet_NaN();
4261
4262 bool foundInception = false;
4263
4264 // Streamer criterion
4265 for (size_t i = 0; i < K.size() - 1; i++) {
4266 if (K[i] <= Kinc && K[i + 1] > Kinc) {
4267 streamerInc = V[i] + (Kinc - K[i]) * (V[i + 1] - V[i]) / (K[i + 1] - K[i]);
4268
4269 break;
4270 }
4271 else if (K[i] == Kinc) {
4272 streamerInc = V[i];
4273
4274 break;
4275 }
4276 }
4277
4278 // Townsend criterion
4279 for (size_t i = 0; i < T.size() - 1; i++) {
4280 if (T[i] <= 1.0 && T[i + 1] > 1) {
4281 townsendInc = V[i] + (1.0 - T[i]) * (V[i + 1] - V[i]) / (T[i + 1] - T[i]);
4282
4283 break;
4284 }
4285 else if (T[i] == 1.0) {
4286 townsendInc = V[i];
4287
4288 break;
4289 }
4290 }
4291
4292 Real Uinc;
4293
4294 if (std::isnan(streamerInc) && std::isnan(townsendInc)) {
4295 Uinc = std::numeric_limits<Real>::quiet_NaN();
4296 }
4297 else if (std::isnan(streamerInc) && !std::isnan(townsendInc)) {
4298 Uinc = townsendInc;
4299 }
4300 else if (!std::isnan(streamerInc) && std::isnan(townsendInc)) {
4301 Uinc = streamerInc;
4302 }
4303 else {
4304 Uinc = std::min(streamerInc, townsendInc);
4305 }
4306
4307 return std::array<Real, 3>{Uinc, streamerInc, townsendInc};
4308 };
4309
4310 // Same functionality as above, but without interpolation.
4311 auto calcUincNoInterp = [Kinc = this->m_inceptionK,
4312 &V = this->m_voltageSweeps](const std::vector<Real>& K,
4313 const std::vector<Real>& T) -> std::array<Real, 3> {
4314 Real streamerInc = std::numeric_limits<Real>::quiet_NaN();
4315 Real townsendInc = std::numeric_limits<Real>::quiet_NaN();
4316
4317 // Streamer criterion
4318 for (size_t i = 0; i < K.size() - 1; i++) {
4319 if (K[i] <= Kinc && K[i + 1] >= Kinc) {
4320 streamerInc = V[i];
4321
4322 break;
4323 }
4324 }
4325
4326 // Townsend criterion
4327 for (size_t i = 0; i < T.size() - 1; i++) {
4328 if (T[i] <= 1.0 && T[i + 1] >= 1) {
4329 townsendInc = V[i];
4330
4331 break;
4332 }
4333 }
4334
4335 Real Uinc;
4336
4337 if (std::isnan(streamerInc) && std::isnan(townsendInc)) {
4338 Uinc = std::numeric_limits<Real>::quiet_NaN();
4339 }
4340 else if (std::isnan(streamerInc) && !std::isnan(townsendInc)) {
4341 Uinc = townsendInc;
4342 }
4343 else if (!std::isnan(streamerInc) && std::isnan(townsendInc)) {
4344 Uinc = streamerInc;
4345 }
4346 else {
4347 Uinc = std::min(streamerInc, townsendInc);
4348 }
4349
4350 return std::array<Real, 3>{Uinc, streamerInc, townsendInc};
4351 };
4352
4353 // Iterate through m_inceptionIntegral data and calculate the inception voltage.
4354 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4355 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4356 const DataIterator& dit = dbl.dataIterator();
4357
4358 const int nbox = dit.size();
4359
4360#pragma omp parallel for schedule(runtime)
4361 for (int mybox = 0; mybox < nbox; mybox++) {
4362 const DataIndex& din = dit[mybox];
4363
4364 EBCellFAB& inceptionVoltagePlus = (*m_inceptionVoltagePlus[lvl])[din];
4365 EBCellFAB& inceptionVoltageMinu = (*m_inceptionVoltageMinu[lvl])[din];
4366 EBCellFAB& streamerInceptionVoltagePlus = (*m_streamerInceptionVoltagePlus[lvl])[din];
4367 EBCellFAB& streamerInceptionVoltageMinu = (*m_streamerInceptionVoltageMinu[lvl])[din];
4368 EBCellFAB& townsendInceptionVoltagePlus = (*m_townsendInceptionVoltagePlus[lvl])[din];
4369 EBCellFAB& townsendInceptionVoltageMinu = (*m_townsendInceptionVoltageMinu[lvl])[din];
4370
4371 FArrayBox& inceptionVoltagePlusReg = inceptionVoltagePlus.getFArrayBox();
4372 FArrayBox& inceptionVoltageMinuReg = inceptionVoltageMinu.getFArrayBox();
4373 FArrayBox& streamerInceptionVoltagePlusReg = streamerInceptionVoltagePlus.getFArrayBox();
4374 FArrayBox& streamerInceptionVoltageMinuReg = streamerInceptionVoltageMinu.getFArrayBox();
4375 FArrayBox& townsendInceptionVoltagePlusReg = townsendInceptionVoltagePlus.getFArrayBox();
4376 FArrayBox& townsendInceptionVoltageMinuReg = townsendInceptionVoltageMinu.getFArrayBox();
4377
4378 // Pack the inception integral and townsend criterions into workable vectors.
4379 Vector<const EBCellFAB*> inceptionIntegralPlus;
4380 Vector<const EBCellFAB*> inceptionIntegralMinu;
4381 Vector<const EBCellFAB*> townsendCriterionPlus;
4382 Vector<const EBCellFAB*> townsendCriterionMinu;
4383
4384 Vector<const FArrayBox*> inceptionIntegralPlusReg;
4385 Vector<const FArrayBox*> inceptionIntegralMinuReg;
4386 Vector<const FArrayBox*> townsendCriterionPlusReg;
4387 Vector<const FArrayBox*> townsendCriterionMinuReg;
4388
4389 for (int i = 0; i < m_voltageSweeps.size(); i++) {
4390 inceptionIntegralPlus.push_back(&(*(m_inceptionIntegralPlus[i])[lvl])[din]);
4391 inceptionIntegralMinu.push_back(&(*(m_inceptionIntegralMinu[i])[lvl])[din]);
4392 townsendCriterionPlus.push_back(&(*(m_townsendCriterionPlus[i])[lvl])[din]);
4393 townsendCriterionMinu.push_back(&(*(m_townsendCriterionMinu[i])[lvl])[din]);
4394
4395 inceptionIntegralPlusReg.push_back(&(inceptionIntegralPlus.back()->getFArrayBox()));
4396 inceptionIntegralMinuReg.push_back(&(inceptionIntegralMinu.back()->getFArrayBox()));
4397 townsendCriterionPlusReg.push_back(&(townsendCriterionPlus.back()->getFArrayBox()));
4398 townsendCriterionMinuReg.push_back(&(townsendCriterionMinu.back()->getFArrayBox()));
4399 }
4400
4401 // Regular kernel.
4402 auto regularKernel = [&](const IntVect& iv) -> void {
4403 std::vector<Real> Kplus;
4404 std::vector<Real> Kminu;
4405
4406 std::vector<Real> Tplus;
4407 std::vector<Real> Tminu;
4408
4409 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
4410 Kplus.emplace_back((*inceptionIntegralPlusReg[i])(iv, 0));
4411 Kminu.emplace_back((*inceptionIntegralMinuReg[i])(iv, 0));
4412
4413 Tplus.emplace_back((*townsendCriterionPlusReg[i])(iv, 0));
4414 Tminu.emplace_back((*townsendCriterionMinuReg[i])(iv, 0));
4415 }
4416
4417 std::array<Real, 3> inceptionVoltagesPlus;
4418 std::array<Real, 3> inceptionVoltagesMinu;
4419
4420 if (m_fullIntegration) {
4421 inceptionVoltagesPlus = calcUincInterp(Kplus, Tplus);
4422 inceptionVoltagesMinu = calcUincInterp(Kminu, Tminu);
4423 }
4424 else {
4425 inceptionVoltagesPlus = calcUincNoInterp(Kplus, Tplus);
4426 inceptionVoltagesMinu = calcUincNoInterp(Kminu, Tminu);
4427 }
4428
4429 inceptionVoltagePlusReg(iv, comp) = std::get<0>(inceptionVoltagesPlus);
4430 inceptionVoltageMinuReg(iv, comp) = std::get<0>(inceptionVoltagesMinu);
4431
4432 streamerInceptionVoltagePlusReg(iv, comp) = std::get<1>(inceptionVoltagesPlus);
4433 streamerInceptionVoltageMinuReg(iv, comp) = std::get<1>(inceptionVoltagesMinu);
4434
4435 townsendInceptionVoltagePlusReg(iv, comp) = std::get<2>(inceptionVoltagesPlus);
4436 townsendInceptionVoltageMinuReg(iv, comp) = std::get<2>(inceptionVoltagesMinu);
4437 };
4438
4439 // Irregular kernel.
4440 auto irregularKernel = [&](const VolIndex& vof) -> void {
4441 std::vector<Real> Kplus;
4442 std::vector<Real> Kminu;
4443
4444 std::vector<Real> Tplus;
4445 std::vector<Real> Tminu;
4446
4447 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
4448 Kplus.emplace_back((*inceptionIntegralPlus[i])(vof, 0));
4449 Kminu.emplace_back((*inceptionIntegralMinu[i])(vof, 0));
4450
4451 Tplus.emplace_back((*townsendCriterionPlus[i])(vof, 0));
4452 Tminu.emplace_back((*townsendCriterionMinu[i])(vof, 0));
4453 }
4454
4455 std::array<Real, 3> inceptionVoltagesPlus;
4456 std::array<Real, 3> inceptionVoltagesMinu;
4457
4458 if (m_fullIntegration) {
4459 inceptionVoltagesPlus = calcUincInterp(Kplus, Tplus);
4460 inceptionVoltagesMinu = calcUincInterp(Kminu, Tminu);
4461 }
4462 else {
4463 inceptionVoltagesPlus = calcUincNoInterp(Kplus, Tplus);
4464 inceptionVoltagesMinu = calcUincNoInterp(Kminu, Tminu);
4465 }
4466
4467 inceptionVoltagePlus(vof, comp) = std::get<0>(inceptionVoltagesPlus);
4468 inceptionVoltageMinu(vof, comp) = std::get<0>(inceptionVoltagesMinu);
4469
4470 streamerInceptionVoltagePlus(vof, comp) = std::get<1>(inceptionVoltagesPlus);
4471 streamerInceptionVoltageMinu(vof, comp) = std::get<1>(inceptionVoltagesMinu);
4472
4473 townsendInceptionVoltagePlus(vof, comp) = std::get<2>(inceptionVoltagesPlus);
4474 townsendInceptionVoltageMinu(vof, comp) = std::get<2>(inceptionVoltagesMinu);
4475 };
4476
4477 // Kernel regions.
4478 const Box& cellBox = dbl[din];
4479 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4480
4481 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4482 BoxLoops::loop(vofit, irregularKernel);
4483 }
4484 }
4485
4486 // Coarsen data.
4487 m_amr->conservativeAverage(m_inceptionVoltagePlus, m_realm, m_phase);
4488 m_amr->interpGhost(m_inceptionVoltageMinu, m_realm, m_phase);
4489 }
4490}
4491
4492template <typename P, typename F, typename C>
4493std::pair<Real, RealVect>
4495{
4496 CH_TIME("DischargeInceptionStepper::computeMinimumInceptionVoltage");
4497 if (m_verbosity > 5) {
4498 pout() << "DischargeInceptionStepper::computeMinimumInceptionVoltage" << endl;
4499 }
4500
4501 const RealVect probLo = m_amr->getProbLo();
4502
4503 std::pair<Real, RealVect> UxInc;
4504
4505 UxInc.first = std::numeric_limits<Real>::max();
4506 UxInc.second = RealVect::Zero;
4507
4508 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
4509 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4510 const DataIterator& dit = dbl.dataIterator();
4511 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4512 const Real& dx = m_amr->getDx()[lvl];
4513
4514 const int nbox = dit.size();
4515
4516#pragma omp parallel for schedule(runtime) reduction(pairmin : UxInc)
4517 for (int mybox = 0; mybox < nbox; mybox++) {
4518 const DataIndex& din = dit[mybox];
4519
4520 const BaseFab<bool>& validCells = (*m_amr->getValidCells(m_realm)[lvl])[din];
4521 const EBISBox& ebisBox = ebisl[din];
4522
4523 const EBCellFAB& voltage = (*a_Uinc[lvl])[din];
4524 const FArrayBox& voltageReg = voltage.getFArrayBox();
4525
4526 auto regularKernel = [&](const IntVect& iv) -> void {
4527 if (validCells(iv, 0) && ebisBox.isRegular(iv)) {
4528 const Real& U = voltageReg(iv, 0);
4529 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv)) * dx;
4530
4531 if (!(std::isnan(U))) {
4532 if (std::abs(U) < UxInc.first) {
4533 UxInc.first = std::abs(U);
4534 UxInc.second = pos;
4535 }
4536 }
4537 }
4538 };
4539
4540 auto irregularKernel = [&](const VolIndex& vof) -> void {
4541 const IntVect iv = vof.gridIndex();
4542 if (validCells(iv, 0) && ebisBox.isIrregular(iv)) {
4543 const Real& U = voltage(vof, 0);
4544 const RealVect centroid = ebisBox.centroid(vof);
4545 const RealVect pos = probLo + (0.5 * RealVect::Unit + RealVect(iv) + centroid) * dx;
4546
4547 if (!(std::isnan(U))) {
4548 if (std::abs(U) < UxInc.first) {
4549 UxInc.first = std::abs(U);
4550 UxInc.second = pos;
4551 }
4552 }
4553 }
4554 };
4555
4556 Box cellBox = dbl[din];
4557 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4558
4559 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4560 BoxLoops::loop(vofit, irregularKernel);
4561 }
4562 }
4563
4564 return ParallelOps::min(UxInc.first, UxInc.second);
4565}
4566
4567template <typename P, typename F, typename C>
4568void
4570{
4571 CH_TIME("DischargeInceptionStepper::computeCriticalVolumeStationary");
4572 if (m_verbosity > 5) {
4573 pout() << "DischargeInceptionStepper::computeCriticalVolumeStationary" << endl;
4574 }
4575
4576 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
4577 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
4578 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
4579
4580 m_criticalVolumePlus.resize(0);
4581 m_criticalVolumeMinu.resize(0);
4582
4583 const int numVoltages = m_inceptionIntegralPlus.size();
4584
4585 // Solve critical volume of K values for each voltage
4586 for (size_t i = 0; i < numVoltages; i++) {
4587 Real criticalVolumePlus = 0.0;
4588 Real criticalVolumeMinu = 0.0;
4589
4590 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4591 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4592 const DataIterator& dit = dbl.dataIterator();
4593 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4594
4595 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4596
4597 const Real dx = m_amr->getDx()[lvl];
4598
4599 const int nbox = dit.size();
4600
4601#pragma omp parallel for schedule(runtime) reduction(+ : criticalVolumePlus, criticalVolumeMinu)
4602 for (int mybox = 0; mybox < nbox; mybox++) {
4603 const DataIndex& din = dit[mybox];
4604
4605 const EBISBox& ebisbox = ebisl[din];
4606 const BaseFab<bool>& validCells = validCellsLD[din];
4607
4608 const EBCellFAB& inceptionIntegralPlus = (*(m_inceptionIntegralPlus[i])[lvl])[din];
4609 const EBCellFAB& inceptionIntegralMinu = (*(m_inceptionIntegralMinu[i])[lvl])[din];
4610
4611 const FArrayBox& inceptionIntegralPlusReg = inceptionIntegralPlus.getFArrayBox();
4612 const FArrayBox& inceptionIntegralMinuReg = inceptionIntegralMinu.getFArrayBox();
4613
4614 const EBCellFAB& townsendCritPlus = (*(m_townsendCriterionPlus[i])[lvl])[din];
4615 const EBCellFAB& townsendCritMinu = (*(m_townsendCriterionMinu[i])[lvl])[din];
4616
4617 const FArrayBox& townsendCritPlusReg = townsendCritPlus.getFArrayBox();
4618 const FArrayBox& townsendCritMinuReg = townsendCritMinu.getFArrayBox();
4619
4620 auto regularKernel = [&](const IntVect& iv) -> void {
4621 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
4622 if (inceptionIntegralPlusReg(iv, 0) >= m_inceptionK || townsendCritPlusReg(iv, 0) >= 1.0) {
4623 criticalVolumePlus += std::pow(dx, SpaceDim);
4624 }
4625 if (inceptionIntegralMinuReg(iv, 0) >= m_inceptionK || townsendCritMinuReg(iv, 0) >= 1.0) {
4626 criticalVolumeMinu += std::pow(dx, SpaceDim);
4627 }
4628 }
4629 };
4630
4631 auto irregularKernel = [&](const VolIndex& vof) -> void {
4632 if (validCells(vof.gridIndex())) {
4633 const Real kappa = ebisbox.volFrac(vof);
4634
4635 if (inceptionIntegralPlus(vof, 0) >= m_inceptionK || townsendCritPlus(vof, 0) >= 1.0) {
4636 criticalVolumePlus += kappa * std::pow(dx, SpaceDim);
4637 }
4638 if (inceptionIntegralMinu(vof, 0) >= m_inceptionK || townsendCritMinu(vof, 0) >= 1.0) {
4639 criticalVolumeMinu += kappa * std::pow(dx, SpaceDim);
4640 }
4641 }
4642 };
4643
4644 // Kernel regions.
4645 const Box cellBox = dbl[din];
4646 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4647
4648 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4649 BoxLoops::loop(vofit, irregularKernel);
4650 }
4651 }
4652
4653 m_criticalVolumePlus.push_back(ParallelOps::sum(criticalVolumePlus));
4654 m_criticalVolumeMinu.push_back(ParallelOps::sum(criticalVolumeMinu));
4655 }
4656}
4657
4658template <typename P, typename F, typename C>
4659Real
4661{
4662 CH_TIME("DischargeInceptionStepper::computeCriticalVolumeTransient");
4663 if (m_verbosity > 5) {
4664 pout() << "DischargeInceptionStepper::computeCriticalVolumeTransient" << endl;
4665 }
4666
4667 Real Vcr = 0.0;
4668
4669 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4670 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4671 const DataIterator& dit = dbl.dataIterator();
4672 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4673
4674 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4675
4676 const Real dx = m_amr->getDx()[lvl];
4677 const Real vol = std::pow(dx, SpaceDim);
4678
4679 const int nbox = dit.size();
4680
4681#pragma omp parallel for schedule(runtime) reduction(+ : Vcr)
4682 for (int mybox = 0; mybox < nbox; mybox++) {
4683 const DataIndex& din = dit[mybox];
4684
4685 const EBISBox& ebisbox = ebisl[din];
4686 const BaseFab<bool>& validCells = validCellsLD[din];
4687
4688 const EBCellFAB& inceptionIntegral = (*m_inceptionIntegral[lvl])[din];
4689 const FArrayBox& inceptionIntegralReg = inceptionIntegral.getFArrayBox();
4690
4691 const EBCellFAB& townsendCriterion = (*m_townsendCriterion[lvl])[din];
4692 const FArrayBox& townsendCriterionReg = townsendCriterion.getFArrayBox();
4693
4694 auto regularKernel = [&](const IntVect& iv) -> void {
4695 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
4696 if (inceptionIntegralReg(iv, 0) >= m_inceptionK || townsendCriterionReg(iv, 0) >= 1.0) {
4697 Vcr += vol;
4698 }
4699 }
4700 };
4701
4702 auto irregularKernel = [&](const VolIndex& vof) -> void {
4703 if (validCells(vof.gridIndex())) {
4704 if (inceptionIntegral(vof, 0) >= m_inceptionK || townsendCriterion(vof, 0) >= 1.0) {
4705 Vcr += ebisbox.volFrac(vof) * vol;
4706 }
4707 }
4708 };
4709
4710 // Kernel regions.
4711 const Box cellBox = dbl[din];
4712 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4713
4714 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4715 BoxLoops::loop(vofit, irregularKernel);
4716 }
4717 }
4718
4719 return ParallelOps::sum(Vcr);
4720}
4721
4722template <typename P, typename F, typename C>
4723void
4725{
4726 CH_TIME("DischargeInceptionStepper::computeCriticalAreaStationary");
4727 if (m_verbosity > 5) {
4728 pout() << "DischargeInceptionStepper::computeCriticalAreaStationary" << endl;
4729 }
4730
4731 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
4732 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
4733 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
4734
4735 m_criticalAreaPlus.resize(0);
4736 m_criticalAreaMinu.resize(0);
4737
4738 const int numVoltages = m_inceptionIntegralPlus.size();
4739
4740 // Solve critical area of K values for each voltage
4741 for (size_t i = 0; i < numVoltages; i++) {
4742 Real criticalAreaPlus = 0.0;
4743 Real criticalAreaMinu = 0.0;
4744
4745 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4746 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4747 const DataIterator& dit = dbl.dataIterator();
4748 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4749
4750 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4751
4752 const Real dx = m_amr->getDx()[lvl];
4753
4754 const int nbox = dit.size();
4755
4756#pragma omp parallel for schedule(runtime) reduction(+ : criticalAreaPlus, criticalAreaMinu)
4757 for (int mybox = 0; mybox < nbox; mybox++) {
4758 const DataIndex& din = dit[mybox];
4759
4760 const EBISBox& ebisbox = ebisl[din];
4761 const BaseFab<bool>& validCells = validCellsLD[din];
4762
4763 const EBCellFAB& inceptionIntegralPlus = (*(m_inceptionIntegralPlus[i])[lvl])[din];
4764 const EBCellFAB& inceptionIntegralMinu = (*(m_inceptionIntegralMinu[i])[lvl])[din];
4765
4766 const EBCellFAB& townsendCriterionPlus = (*(m_townsendCriterionPlus[i])[lvl])[din];
4767 const EBCellFAB& townsendCriterionMinu = (*(m_townsendCriterionMinu[i])[lvl])[din];
4768
4769 auto irregularKernel = [&](const VolIndex& vof) -> void {
4770 if (validCells(vof.gridIndex())) {
4771 const Real boundaryArea = ebisbox.bndryArea(vof);
4772
4773 if (inceptionIntegralPlus(vof, 0) >= m_inceptionK || townsendCriterionPlus(vof, 0) >= 1.0) {
4774 criticalAreaPlus += boundaryArea * std::pow(dx, SpaceDim - 1);
4775 }
4776 if (inceptionIntegralMinu(vof, 0) >= m_inceptionK || townsendCriterionMinu(vof, 0) >= 1.0) {
4777 criticalAreaMinu += boundaryArea * std::pow(dx, SpaceDim - 1);
4778 }
4779 }
4780 };
4781
4782 // Kernel regions.
4783 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4784
4785 BoxLoops::loop(vofit, irregularKernel);
4786 }
4787 }
4788
4789 m_criticalAreaPlus.push_back(ParallelOps::sum(criticalAreaPlus));
4790 m_criticalAreaMinu.push_back(ParallelOps::sum(criticalAreaMinu));
4791 }
4792}
4793
4794template <typename P, typename F, typename C>
4795Real
4797{
4798 CH_TIME("DischargeInceptionStepper::computeCriticalAreaTransient");
4799 if (m_verbosity > 5) {
4800 pout() << "DischargeInceptionStepper::computeCriticalAreaTransient" << endl;
4801 }
4802
4803 Real Acr = 0.0;
4804
4805 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4806 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4807 const DataIterator& dit = dbl.dataIterator();
4808 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4809
4810 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4811
4812 const Real dx = m_amr->getDx()[lvl];
4813 const Real area = std::pow(dx, SpaceDim - 1);
4814
4815 const int nbox = dit.size();
4816
4817#pragma omp parallel for schedule(runtime) reduction(+ : Acr)
4818 for (int mybox = 0; mybox < nbox; mybox++) {
4819 const DataIndex& din = dit[mybox];
4820
4821 const EBISBox& ebisbox = ebisl[din];
4822 const BaseFab<bool>& validCells = validCellsLD[din];
4823
4824 const EBCellFAB& inceptionIntegral = (*m_inceptionIntegral[lvl])[din];
4825 const EBCellFAB& townsendCriterion = (*m_townsendCriterion[lvl])[din];
4826
4827 auto irregularKernel = [&](const VolIndex& vof) -> void {
4828 if (validCells(vof.gridIndex())) {
4829 if (inceptionIntegral(vof, 0) >= m_inceptionK || townsendCriterion(vof, 0) >= 1.0) {
4830 Acr += ebisbox.bndryArea(vof) * area;
4831 }
4832 }
4833 };
4834
4835 // Kernel regions.
4836 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4837
4838 BoxLoops::loop(vofit, irregularKernel);
4839 }
4840 }
4841
4842 return ParallelOps::sum(Acr);
4843}
4844
4845template <typename P, typename F, typename C>
4846void
4848{
4849 CH_TIME("DischargeInceptionStepper::computeIonizationVolumeStationary");
4850 if (m_verbosity > 5) {
4851 pout() << "DischargeInceptionStepper::computeIonizationVolumeStationary" << endl;
4852 }
4853
4854 CH_assert(m_inceptionIntegralPlus.size() == m_inceptionIntegralMinu.size());
4855 CH_assert(m_inceptionIntegralPlus.size() == m_KPlusValues.size());
4856 CH_assert(m_inceptionIntegralMinu.size() == m_KMinuValues.size());
4857
4858 m_ionizationVolume.resize(0);
4859
4860 const int numVoltages = m_inceptionIntegralPlus.size();
4861
4862 // Storage something that holds the electric filed
4863 EBAMRCellData scratch;
4864 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
4865
4866 // Solve ionization volume for each voltage
4867 for (size_t i = 0; i < m_voltageSweeps.size(); i++) {
4868 const Real voltage = m_voltageSweeps[i];
4869
4870 Real ionizationVolume = 0.0;
4871
4872 this->superposition(scratch, voltage);
4873
4874 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4875 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4876 const DataIterator& dit = dbl.dataIterator();
4877 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4878
4879 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4880
4881 const Real dx = m_amr->getDx()[lvl];
4882 const RealVect probLo = m_amr->getProbLo();
4883
4884 const int nbox = dit.size();
4885
4886#pragma omp parallel for schedule(runtime) reduction(+ : ionizationVolume)
4887 for (int mybox = 0; mybox < nbox; mybox++) {
4888 const DataIndex& din = dit[mybox];
4889
4890 const EBISBox& ebisbox = ebisl[din];
4891 const BaseFab<bool>& validCells = validCellsLD[din];
4892
4893 const EBCellFAB& electricField = (*scratch[lvl])[din];
4894 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
4895
4896 auto regularKernel = [&](const IntVect& iv) -> void {
4897 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
4898 const RealVect x = probLo + dx * (0.5 * RealVect::Unit + RealVect(iv));
4899 const RealVect EE = RealVect(
4900 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
4901 const Real E = EE.vectorLength();
4902
4903 const Real alpha = m_alpha(E, x);
4904 const Real eta = m_eta(E, x);
4905
4906 if (alpha >= eta) {
4907 ionizationVolume += std::pow(dx, SpaceDim);
4908 }
4909 }
4910 };
4911
4912 auto irregularKernel = [&](const VolIndex& vof) -> void {
4913 if (validCells(vof.gridIndex())) {
4914
4915 const RealVect x = probLo + Location::position(Location::Cell::Center, vof, ebisbox, dx);
4916 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
4917 const Real E = EE.vectorLength();
4918
4919 const Real alpha = m_alpha(E, x);
4920 const Real eta = m_eta(E, x);
4921
4922 const Real kappa = ebisbox.volFrac(vof);
4923
4924 if (alpha >= eta) {
4925 ionizationVolume += kappa * std::pow(dx, SpaceDim);
4926 }
4927 }
4928 };
4929
4930 // Kernel regions.
4931 const Box cellBox = dbl[din];
4932 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
4933
4934 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
4935 BoxLoops::loop(vofit, irregularKernel);
4936 }
4937 }
4938
4939 m_ionizationVolume.push_back(ParallelOps::sum(ionizationVolume));
4940 }
4941}
4942
4943template <typename P, typename F, typename C>
4944Real
4946{
4947 CH_TIME("DischargeInceptionStepper::computeIonizationVolumeTransient");
4948 if (m_verbosity > 5) {
4949 pout() << "DischargeInceptionStepper::computeIonizationVolumeTransient" << endl;
4950 }
4951
4952 Real Vion = 0.0;
4953
4954 // Calculate electric field
4955 EBAMRCellData scratch;
4956 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
4957 this->superposition(scratch, a_voltage);
4958
4959 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); ++lvl) {
4960 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
4961 const DataIterator& dit = dbl.dataIterator();
4962 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
4963
4964 const LevelData<BaseFab<bool>>& validCellsLD = *m_amr->getValidCells(m_realm)[lvl];
4965
4966 const Real dx = m_amr->getDx()[lvl];
4967 const Real vol = std::pow(dx, SpaceDim);
4968 const RealVect probLo = m_amr->getProbLo();
4969
4970 const int nbox = dit.size();
4971
4972#pragma omp parallel for schedule(runtime) reduction(+ : Vion)
4973 for (int mybox = 0; mybox < nbox; mybox++) {
4974 const DataIndex& din = dit[mybox];
4975
4976 const EBISBox& ebisbox = ebisl[din];
4977 const BaseFab<bool>& validCells = validCellsLD[din];
4978
4979 const EBCellFAB& electricField = (*scratch[lvl])[din];
4980 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
4981
4982 auto regularKernel = [&](const IntVect& iv) -> void {
4983 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
4984
4985 const RealVect x = probLo + dx * (0.5 * RealVect::Unit + RealVect(iv));
4986 const RealVect EE = RealVect(
4987 D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
4988 const Real E = EE.vectorLength();
4989
4990 const Real alpha = m_alpha(E, x);
4991 const Real eta = m_eta(E, x);
4992 if (alpha >= eta) {
4993 Vion += vol;
4994 }
4995 }
4996 };
4997
4998 auto irregularKernel = [&](const VolIndex& vof) -> void {
4999 if (validCells(vof.gridIndex())) {
5000
5001 const RealVect x = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
5002 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
5003 const Real E = EE.vectorLength();
5004
5005 const Real alpha = m_alpha(E, x);
5006 const Real eta = m_eta(E, x);
5007 if (alpha >= eta) {
5008 Vion += ebisbox.volFrac(vof) * vol;
5009 }
5010 }
5011 };
5012
5013 // Kernel regions.
5014 const Box cellBox = dbl[din];
5015 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
5016
5017 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
5018 BoxLoops::loop(vofit, irregularKernel);
5019 }
5020 }
5021
5022 return ParallelOps::sum(Vion);
5023}
5024
5025template <typename P, typename F, typename C>
5026void
5028{
5029 CH_TIME("DischargeInceptionStepper::writeReportStationary");
5030 if (m_verbosity > 5) {
5031 pout() << "DischargeInceptionStepper::writeReportStationary" << endl;
5032 }
5033
5034 const auto UIncPlus = this->computeMinimumInceptionVoltage(m_inceptionVoltagePlus);
5035 const auto UIncMinu = this->computeMinimumInceptionVoltage(m_inceptionVoltageMinu);
5036
5037 const auto streamerUIncPlus = this->computeMinimumInceptionVoltage(m_streamerInceptionVoltagePlus);
5038 const auto streamerUIncMinu = this->computeMinimumInceptionVoltage(m_streamerInceptionVoltageMinu);
5039
5040 const auto townsendUIncPlus = this->computeMinimumInceptionVoltage(m_townsendInceptionVoltagePlus);
5041 const auto townsendUIncMinu = this->computeMinimumInceptionVoltage(m_townsendInceptionVoltageMinu);
5042
5043#ifdef CH_MPI
5044 if (procID() == 0) {
5045#endif
5046
5047 std::ofstream output(m_outputFile, std::ofstream::out);
5048
5049 const int ww = (SpaceDim == 2) ? 24 : 36;
5050
5051 const std::string lineBreak = "# " + std::string(178 + 4 * ww, '=');
5052
5053 // clang-format off
5054 output << lineBreak << "\n";
5055 if (std::isnan(UIncPlus.first) || std::isnan(UIncMinu.first)) {
5056 output << "# Could not compute inception voltage\n";
5057 }
5058 else {
5059 if(m_fullIntegration){
5060 output << "# Minimum inception voltage(+) = " << UIncPlus.first << ",\t x = " << UIncPlus.second << "\n";
5061 output << "# Minimum inception voltage(-) = " << UIncMinu.first << ",\t x = " << UIncMinu.second << "\n";
5062 output << "# \n";
5063 output << "# Streamer inception voltage(+) = " << streamerUIncPlus.first << ",\t x = " << streamerUIncPlus.second << "\n";
5064 output << "# Streamer inception voltage(-) = " << streamerUIncMinu.first << ",\t x = " << streamerUIncMinu.second << "\n";
5065 output << "# \n";
5066 output << "# Townsend inception voltage(+) = " << townsendUIncPlus.first << ",\t x = " << townsendUIncPlus.second << "\n";
5067 output << "# Townsend inception voltage(-) = " << townsendUIncMinu.first << ",\t x = " << townsendUIncMinu.second << "\n";
5068 }
5069 else{
5070 output << "# Minimum inception voltage(+) >= " << UIncPlus.first << ",\t x = " << UIncPlus.second << "\n";
5071 output << "# Minimum inception voltage(-) >= " << UIncMinu.first << ",\t x = " << UIncMinu.second << "\n";
5072 output << "# \n";
5073 output << "# Streamer inception voltage(+) >= " << streamerUIncPlus.first << ",\t x = " << streamerUIncPlus.second << "\n";
5074 output << "# Streamer inception voltage(-) >= " << streamerUIncMinu.first << ",\t x = " << streamerUIncMinu.second << "\n";
5075 output << "# \n";
5076 output << "# Townsend inception voltage(+) >= " << townsendUIncPlus.first << ",\t x = " << townsendUIncPlus.second << "\n";
5077 output << "# Townsend inception voltage(-) >= " << townsendUIncMinu.first << ",\t x = " << townsendUIncMinu.second << "\n";
5078 }
5079 }
5080
5081 output << lineBreak << "\n";
5082 output << left << setw(15) << setfill(' ') << "# +/- Voltage";
5083 output << left << setw(15) << setfill(' ') << "Max K(+)";
5084 output << left << setw(15) << setfill(' ') << "Max K(-)";
5085 output << left << setw(ww) << setfill(' ') << "Pos. max K(+)";
5086 output << left << setw(ww) << setfill(' ') << "Pos. max K(-)";
5087 output << left << setw(20) << setfill(' ') << "Max T(+)";
5088 output << left << setw(20) << setfill(' ') << "Max T(-)";
5089 output << left << setw(ww) << setfill(' ') << "Pos. max T(+)";
5090 output << left << setw(ww) << setfill(' ') << "Pos. max T(-)";
5091 output << left << setw(20) << setfill(' ') << "Crit. vol(+)";
5092 output << left << setw(20) << setfill(' ') << "Crit. vol(-)";
5093 output << left << setw(20) << setfill(' ') << "Crit. area(+)";
5094 output << left << setw(20) << setfill(' ') << "Crit. area(-)";
5095 output << left << setw(20) << setfill(' ') << "Ionization vol." << "\n";
5096 output << lineBreak << "\n";
5097
5098 auto RealVectToString = [=](const RealVect x) -> std::string {
5099 std::string ret = "(";
5100 for (int dir = 0; dir < SpaceDim; dir++) {
5101 ret += std::to_string(x[dir]);
5102 if(dir < SpaceDim -1) {
5103 ret += ", ";
5104 }
5105 }
5106
5107 ret += ")";
5108
5109 return ret;
5110 };
5111
5112 for (int i = 0; i < m_voltageSweeps.size(); i++) {
5113 output << left << setw(15) << setfill(' ') << m_voltageSweeps[i];
5114 output << left << setw(15) << setfill(' ') << std::get<1>(m_KPlusValues[i]);
5115 output << left << setw(15) << setfill(' ') << std::get<1>(m_KMinuValues[i]);
5116 output << left << setw(ww) << setfill(' ') << RealVectToString(std::get<2>(m_KPlusValues[i]));
5117 output << left << setw(ww) << setfill(' ') << RealVectToString(std::get<2>(m_KMinuValues[i]));
5118 output << left << setw(20) << setfill(' ') << std::get<1>(m_TPlusValues[i]);
5119 output << left << setw(20) << setfill(' ') << std::get<1>(m_TMinuValues[i]);
5120 output << left << setw(ww) << setfill(' ') << RealVectToString(std::get<2>(m_TPlusValues[i]));
5121 output << left << setw(ww) << setfill(' ') << RealVectToString(std::get<2>(m_TMinuValues[i]));
5122 output << left << setw(20) << setfill(' ') << m_criticalVolumePlus[i];
5123 output << left << setw(20) << setfill(' ') << m_criticalVolumeMinu[i];
5124 output << left << setw(20) << setfill(' ') << m_criticalAreaPlus[i];
5125 output << left << setw(20) << setfill(' ') << m_criticalAreaMinu[i];
5126 output << left << setw(20) << setfill(' ') << m_ionizationVolume[i];
5127 output << endl;
5128 }
5129 output << lineBreak << "\n";
5130 // clang-format on
5131#ifdef CH_MPI
5132 }
5133#endif
5134}
5135
5136template <typename P, typename F, typename C>
5137void
5139{
5140 CH_TIME("DischargeInceptionStepper::writeReportTransient");
5141 if (m_verbosity > 5) {
5142 pout() << "DischargeInceptionStepper::writeReportTransient" << endl;
5143 }
5144
5145#ifdef CH_MPI
5146 if (procID() == 0) {
5147#endif
5148 std::ofstream output(m_outputFile, std::ofstream::out);
5149
5150 output << std::left << std::setw(15) << setfill(' ') << "# Time t";
5151 output << std::left << std::setw(15) << setfill(' ') << "V(t)";
5152 output << std::left << std::setw(15) << setfill(' ') << "max K(t)";
5153 output << std::left << std::setw(15) << setfill(' ') << "max T(t)";
5154 output << std::left << std::setw(15) << setfill(' ') << "Vcr(t)";
5155 output << std::left << std::setw(15) << setfill(' ') << "Acr(t)";
5156 output << std::left << std::setw(15) << setfill(' ') << "Vion(t)";
5157 output << std::left << std::setw(15) << setfill(' ') << "lambda(t)";
5158 output << std::left << std::setw(15) << setfill(' ') << "P(t)";
5159 output << std::left << std::setw(15) << setfill(' ') << "dP(t, t+dt)";
5160 output << std::left << std::setw(15) << setfill(' ') << "Time lag";
5161 output << "\n";
5162
5163 // Compute dP(t, t+dt)
5164 std::vector<Real> dProb;
5165 for (size_t i = 0; i < m_Rdot.size() - 1; i++) {
5166 const Real t = m_Rdot[i].first;
5167 const Real dt = m_Rdot[i + 1].first - m_Rdot[i].first;
5168 const Real prob = m_inceptionProbability[i].second;
5169 const Real Rdot = m_Rdot[i].second;
5170
5171 dProb.emplace_back((1.0 - prob) * Rdot * dt);
5172 }
5173 dProb.emplace_back(0.0);
5174
5175 // Compute the average waiting time.
5176 std::vector<Real> tau(m_Rdot.size(), 0.0);
5177 for (size_t i = 0; i < m_Rdot.size() - 1; i++) {
5178 const Real t1 = m_Rdot[i].first;
5179 const Real t2 = m_Rdot[i + 1].first;
5180 const Real dt = t2 - t1;
5181 const Real prob = m_inceptionProbability[i].second;
5182 const Real lambda = m_Rdot[i].second;
5183
5184 tau[i + 1] = tau[i] + t1 * (1 - prob) * lambda * dt;
5185 }
5186 for (int i = 0; i < tau.size(); i++) {
5187 const Real prob = m_inceptionProbability[i].second;
5188
5189 tau[i] = prob > 0.0 ? tau[i] / prob : std::numeric_limits<Real>::infinity();
5190 }
5191
5192 for (size_t i = 0; i < m_Rdot.size(); i++) {
5193 const Real time = m_Rdot[i].first;
5194
5195 output << std::left << std::setw(15) << time;
5196 output << std::left << std::setw(15) << m_voltageCurve(time);
5197 output << std::left << std::setw(15) << m_maxK[i].second;
5198 output << std::left << std::setw(15) << m_maxT[i].second;
5199 output << std::left << std::setw(15) << m_criticalVolume[i].second;
5200 output << std::left << std::setw(15) << m_criticalArea[i].second;
5201 output << std::left << std::setw(15) << m_ionizationVolumeTransient[i].second;
5202 output << std::left << std::setw(15) << m_Rdot[i].second;
5203 output << std::left << std::setw(15) << m_inceptionProbability[i].second;
5204 output << std::left << std::setw(15) << dProb[i];
5205 output << std::left << std::setw(15) << tau[i];
5206 output << "\n";
5207 }
5208
5209 output.close();
5210
5211#ifdef CH_MPI
5212 }
5213#endif
5214}
5215
5216template <typename P, typename F, typename C>
5217bool
5219 const RealVect& a_probLo,
5220 const RealVect& a_probHi) const noexcept
5221{
5222#ifndef NDEBUG
5223 CH_TIME("DischargeInceptionStepper::particleOutsideGrid");
5224 if (m_verbosity > 5) {
5225 pout() << "DischargeInceptionStepper::particleOutsideGrid" << endl;
5226 }
5227#endif
5228
5229 bool isOutside = false;
5230
5231 for (int dir = 0; dir < SpaceDim; dir++) {
5232 if (a_pos[dir] <= a_probLo[dir] || a_pos[dir] >= a_probHi[dir]) {
5233 isOutside = true;
5234 }
5235 }
5236
5237 return isOutside;
5238}
5239
5240template <typename P, typename F, typename C>
5241bool
5242DischargeInceptionStepper<P, F, C>::particleInsideEB(const RealVect& a_pos) const noexcept
5243{
5244#ifndef NDEBUG
5245 CH_TIME("DischargeInceptionStepper::particleInsideEB");
5246 if (m_verbosity > 5) {
5247 pout() << "DischargeInceptionStepper::particleInsideEB" << endl;
5248 }
5249#endif
5250
5251 const RefCountedPtr<BaseIF>& implicitFunction = m_amr->getBaseImplicitFunction(m_phase);
5252
5253 return (implicitFunction->value(a_pos) >= 0.0) ? true : false;
5254}
5255
5256template <typename P, typename F, typename C>
5257void
5259{
5260 CH_TIME("DischargeInceptionStepper::computeIonVelocity");
5261 if (m_verbosity > 5) {
5262 pout() << "DischargeInceptionStepper::computeIonVelocity" << endl;
5263 }
5264
5265 CH_assert(!(m_ionSolver.isNull()));
5266 CH_assert(m_ionSolver->isMobile());
5267
5268 EBAMRCellData& vel = m_ionSolver->getCellCenteredVelocity();
5269
5270 // Compute electric field at the input voltage and set v = -E
5271 this->superposition(vel, a_voltage);
5272 DataOps::scale(vel, -1.0);
5273
5274 // Allocate mesh data that holds mu and compute it on the mesh.
5275 EBAMRCellData mu;
5276 m_amr->allocate(mu, m_realm, m_phase, 1);
5277
5278 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
5279 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
5280 const DataIterator& dit = dbl.dataIterator();
5281
5282 const int nbox = dit.size();
5283
5284#pragma omp parallel for schedule(runtime)
5285 for (int mybox = 0; mybox < nbox; mybox++) {
5286 const DataIndex& din = dit[mybox];
5287
5288 const EBCellFAB& v = (*vel[lvl])[din];
5289 const FArrayBox& vReg = v.getFArrayBox();
5290
5291 EBCellFAB& MU = (*mu[lvl])[din];
5292 FArrayBox& MUREG = MU.getFArrayBox();
5293
5294 auto regularKernel = [&](const IntVect& iv) -> void {
5295 const RealVect EE = RealVect(D_DECL(vReg(iv, 0), vReg(iv, 1), vReg(iv, 2)));
5296 const Real E = EE.vectorLength();
5297
5298 MUREG(iv, 0) = m_ionMobility(E);
5299 };
5300
5301 auto irregularKernel = [&](const VolIndex& vof) -> void {
5302 const RealVect EE = RealVect(D_DECL(v(vof, 0), v(vof, 1), v(vof, 2)));
5303 const Real E = EE.vectorLength();
5304
5305 MU(vof, 0) = m_ionMobility(E);
5306 };
5307
5308 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
5309 Box cellBox = dbl[din];
5310
5311 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
5312 BoxLoops::loop(vofit, irregularKernel);
5313 }
5314 }
5315
5316 DataOps::multiplyScalar(vel, mu);
5317
5318 m_amr->arithmeticAverage(vel, m_realm, m_phase);
5319 m_amr->interpGhostPwl(vel, m_realm, m_phase);
5320}
5321
5322template <typename P, typename F, typename C>
5323void
5325{
5326 CH_TIME("DischargeInceptionStepper::computeIonDiffusion");
5327 if (m_verbosity > 5) {
5328 pout() << "DischargeInceptionStepper::computeIonDiffusion" << endl;
5329 }
5330
5331 CH_assert(!(m_ionSolver.isNull()));
5332 CH_assert(m_ionSolver->isMobile());
5333
5334 // Compute the electric field at the input voltage.
5335 EBAMRCellData scratch;
5336 m_amr->allocate(scratch, m_realm, m_phase, SpaceDim);
5337 this->superposition(scratch, a_voltage);
5338
5339 // Compute the diffusion coefficient on cell centers.
5340 EBAMRCellData diffCoCell;
5341 m_amr->allocate(diffCoCell, m_realm, m_phase, 1);
5342
5343 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
5344 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
5345 const DataIterator& dit = dbl.dataIterator();
5346
5347 const int nbox = dit.size();
5348
5349#pragma omp parallel for schedule(runtime)
5350 for (int mybox = 0; mybox < nbox; mybox++) {
5351 const DataIndex& din = dit[mybox];
5352
5353 const EBCellFAB& electricField = (*scratch[lvl])[din];
5354 const FArrayBox& electricFieldReg = electricField.getFArrayBox();
5355
5356 EBCellFAB& dCo = (*diffCoCell[lvl])[din];
5357 FArrayBox& dCoReg = dCo.getFArrayBox();
5358
5359 auto regularKernel = [&](const IntVect& iv) -> void {
5360 const RealVect EE = RealVect(D_DECL(electricFieldReg(iv, 0), electricFieldReg(iv, 1), electricFieldReg(iv, 2)));
5361 const Real E = EE.vectorLength();
5362
5363 dCoReg(iv, 0) = m_ionDiffusion(E);
5364 };
5365
5366 auto irregularKernel = [&](const VolIndex& vof) -> void {
5367 const RealVect EE = RealVect(D_DECL(electricField(vof, 0), electricField(vof, 1), electricField(vof, 2)));
5368 const Real E = EE.vectorLength();
5369
5370 dCo(vof, 0) = m_ionDiffusion(E);
5371 };
5372
5373 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
5374 Box cellBox = dbl[din];
5375
5376 BoxLoops::loop<D_DECL(1, 1, 1)>(cellBox, regularKernel);
5377 BoxLoops::loop(vofit, irregularKernel);
5378 }
5379 }
5380
5381 // Coarsen and update ghost cells
5382 m_amr->arithmeticAverage(diffCoCell, m_realm, m_phase);
5383 m_amr->interpGhostPwl(diffCoCell, m_realm, m_phase);
5384
5385 // Fetch from solver and average to grid faces.
5386 EBAMRFluxData& diffCoFace = m_ionSolver->getFaceCenteredDiffusionCoefficient();
5387 EBAMRIVData& diffCoEB = m_ionSolver->getEbCenteredDiffusionCoefficient();
5388
5389 DataOps::setValue(diffCoFace, 0.0);
5390 DataOps::setValue(diffCoEB, 0.0); // Neumann BC.
5391
5392 DataOps::averageCellToFace(diffCoFace,
5393 diffCoCell,
5394 m_amr->getDomains(),
5395 1,
5396 Interval(0, 0),
5397 Interval(0, 0),
5398 Average::Arithmetic,
5399 m_amr->getFaceIteratorWithTangentialGhosts(m_realm, m_phase));
5400}
5401
5402template <typename P, typename F, typename C>
5403void
5405 const MFAMRCellData& a_inhomogeneousField,
5406 const MFAMRCellData& a_homogeneousField,
5407 const Real a_voltage) const noexcept
5408{
5409 CH_TIME("DischargeInceptionStepper::superposition(full)");
5410
5411 const EBAMRCellData homogeneousField = m_amr->alias(phase::gas, a_homogeneousField);
5412 const EBAMRCellData inhomogeneousField = m_amr->alias(phase::gas, a_inhomogeneousField);
5413
5414 DataOps::setValue(a_sumField, 0.0);
5415 DataOps::incr(a_sumField, homogeneousField, a_voltage);
5416 DataOps::incr(a_sumField, inhomogeneousField, 1.0);
5417
5418 m_amr->arithmeticAverage(a_sumField, m_realm, m_phase);
5419 m_amr->interpGhostPwl(a_sumField, m_realm, m_phase);
5420 // m_amr->interpToCentroids(a_sumField, m_realm, m_phase);
5421}
5422
5423template <typename P, typename F, typename C>
5424void
5425DischargeInceptionStepper<P, F, C>::superposition(EBAMRCellData& a_sumField, const Real a_voltage) const noexcept
5426{
5427 CH_TIME("DischargeInceptionStepper::superposition(short)");
5428
5429 this->superposition(a_sumField, m_electricFieldInho, m_electricFieldHomo, a_voltage);
5430}
5431
5432template <typename P, typename F, typename C>
5433void
5435 RealVect& a_maxPos,
5436 const EBAMRCellData& a_data) const noexcept
5437{
5438 CH_TIME("DischargeInceptionStepper::getMaxValueAndLocation");
5439 if (m_verbosity > 5) {
5440 pout() << "DischargeInceptionStepper::getMaxValueAndLocation" << endl;
5441 }
5442
5443 // Local (val, pos) accumulator so the OpenMP box loop can use a custom pairmax reduction -- writing the
5444 // shared a_maxVal/a_maxPos directly from the kernels would be a data race across threads.
5445 std::pair<Real, RealVect> maxValAndPos;
5446 maxValAndPos.first = -std::numeric_limits<Real>::max();
5447 maxValAndPos.second = RealVect::Zero;
5448
5449 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
5450 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
5451 const DataIterator& dit = dbl.dataIterator();
5452 const EBISLayout& ebisl = m_amr->getEBISLayout(m_realm, m_phase)[lvl];
5453 const Real dx = m_amr->getDx()[lvl];
5454 const RealVect probLo = m_amr->getProbLo();
5455
5456 const LevelData<BaseFab<bool>>& validCellsLD = (*m_amr->getValidCells(m_realm)[lvl]);
5457
5458 const int nbox = dit.size();
5459
5460#pragma omp parallel for schedule(runtime) reduction(pairmax : maxValAndPos)
5461 for (int mybox = 0; mybox < nbox; mybox++) {
5462 const DataIndex& din = dit[mybox];
5463 const Box& cellbox = dbl[din];
5464 const EBISBox& ebisbox = ebisl[din];
5465
5466 const EBCellFAB& data = (*a_data[lvl])[din];
5467 const FArrayBox& dataReg = data.getFArrayBox();
5468 const BaseFab<bool>& validCells = validCellsLD[din];
5469
5470 CH_assert(data.nComp() == 1);
5471
5472 // Not vectorizable: max+argmax reduction with a data-dependent branch.
5473 auto regularKernel = [&](const IntVect& iv) -> void {
5474 if (validCells(iv, 0) && ebisbox.isRegular(iv)) {
5475 if (dataReg(iv, 0) > maxValAndPos.first) {
5476 maxValAndPos.first = dataReg(iv, 0);
5477 maxValAndPos.second = probLo + (RealVect(iv) + 0.5 * RealVect::Unit) * dx;
5478 }
5479 }
5480 };
5481
5482 auto irregularKernel = [&](const VolIndex& vof) -> void {
5483 if (validCells(vof.gridIndex(), 0) && ebisbox.isIrregular(vof.gridIndex())) {
5484 if (data(vof, 0) > maxValAndPos.first) {
5485 maxValAndPos.first = data(vof, 0);
5486 maxValAndPos.second = probLo + Location::position(Location::Cell::Centroid, vof, ebisbox, dx);
5487 }
5488 }
5489 };
5490
5491 VoFIterator& vofit = (*m_amr->getVofIterator(m_realm, m_phase)[lvl])[din];
5492
5493 BoxLoops::loop<D_DECL(1, 1, 1)>(cellbox, regularKernel);
5494 BoxLoops::loop(vofit, irregularKernel);
5495 }
5496 }
5497
5498 const std::pair<Real, RealVect> globalMaxValAndPos = ParallelOps::max(maxValAndPos.first, maxValAndPos.second);
5499
5500 a_maxVal = globalMaxValAndPos.first;
5501 a_maxPos = globalMaxValAndPos.second;
5502}
5503
5504template <typename P, typename F, typename C>
5505void
5507 int& a_comp,
5508 const EBAMRCellData& a_data,
5509 const std::string a_outputRealm,
5510 const int a_level,
5511 const bool a_interpToCentroids,
5512 const bool a_interpGhost) const noexcept
5513{
5514 CH_TIMERS("DischargeInceptionStepper::writeData");
5515 CH_TIMER("DischargeInceptionStepper::writeData::allocate", t1);
5516 CH_TIMER("DischargeInceptionStepper::writeData::local_copy", t2);
5517 CH_TIMER("DischargeInceptionStepper::writeData::interp_ghost", t3);
5518 CH_TIMER("DischargeInceptionStepper::writeData::interp_centroid", t4);
5519 CH_TIMER("DischargeInceptionStepper::writeData::final_copy", t5);
5520 if (m_verbosity > 5) {
5521 pout() << "DischargeInceptionStepper::writeData" << endl;
5522 }
5523
5524 // Number of components we are working with.
5525 const int numComp = a_data[a_level]->nComp();
5526
5527 // Component ranges that we copy to/from.
5528 const Interval srcInterv(0, numComp - 1);
5529 const Interval dstInterv(a_comp, a_comp + numComp - 1);
5530
5531 CH_START(t1);
5532 LevelData<EBCellFAB> scratch;
5533 m_amr->allocate(scratch, m_realm, m_phase, a_level, numComp);
5534 CH_STOP(t1);
5535
5536 CH_START(t2);
5537 m_amr->copyData(scratch, *a_data[a_level], a_level, m_realm, m_realm);
5538 CH_STOP(t2);
5539
5540 // Interpolate ghost cells
5541 CH_START(t3);
5542 if (a_level > 0 && a_interpGhost) {
5543 m_amr->interpGhost(scratch, *a_data[a_level - 1], a_level, m_realm, m_phase);
5544 }
5545 CH_STOP(t3);
5546
5547 CH_START(t4);
5548 if (a_interpToCentroids) {
5549 m_amr->interpToCentroids(scratch, m_realm, m_phase, a_level);
5550 }
5551 CH_STOP(t4);
5552
5553 DataOps::setCoveredValue(scratch, *m_amr->getCoveredCells(m_realm, m_phase)[a_level], 0.0);
5554
5555 CH_START(t5);
5556 m_amr->copyData(a_output, scratch, a_level, a_outputRealm, m_realm, dstInterv, srcInterv);
5557 CH_STOP(t5);
5558
5559 a_comp += numComp;
5560}
5561
5562template <typename P, typename F, typename C>
5563const EBAMRCellData*
5565{
5566 CH_TIMERS("DischargeInceptionStepper::getElectricField");
5567
5568 return &m_homogeneousFieldGas;
5569}
5570
5571template <typename P, typename F, typename C>
5572Real
5574{
5575 CH_TIME("DischargeInceptionStepper::getCriticalField");
5576 if (m_verbosity > 5) {
5577 pout() << "DischargeInceptionStepper::getCriticalField" << endl;
5578 }
5579
5580 Real Emin = -10.0;
5581 Real Emax = +10.0;
5582
5583 // Quick lambda for the effective Townsend ionization coefficient. Note that we solve for the exponent.
5584 auto alpha = [this](const Real E) -> Real {
5585 return m_alpha(std::pow(10.0, E), RealVect::Zero) - m_eta(std::pow(10.0, E), RealVect::Zero);
5586 };
5587
5588 const Real p = PolyUtils::brentSolve(Emin, Emax, alpha);
5589
5590 return std::pow(10.0, p);
5591}
5592
5593#include <CD_NamespaceFooter.H>
5594
5595#endif
Declaration of the Physics::DischargeInception::DischargeInceptionSpecies CDR species.
Declaration of the Physics::DischargeInception::DischargeInceptionStepper TimeStepper.
Mode
Solver mode: stationary (voltage sweep) or transient (time-dependent).
Definition CD_DischargeInceptionStepper.H:56
Declaration of various useful OpenMP-related utilities.
Declaration of a namespace for SIMD-decorated loops over SoA particles.
CD_PARTICLE_REAL ParticleReal
Floating-point type a user may use for payload columns.
Definition CD_ParticleSoA.H:156
Agglomeration of some useful algebraic/polynomial routines.
Declaration of various useful units.
static void scale(MFAMRCellData &a_lhs, const Real &a_scale) noexcept
Scale data by factor.
Definition CD_DataOps.cpp:2503
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:1465
static void getMaxMin(Real &max, Real &min, EBAMRCellData &a_data, const int a_comp, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Get maximum and minimum value of specified component.
Definition CD_DataOps.cpp:1711
static void getMaxMinNorm(Real &a_max, Real &a_min, EBAMRCellData &data, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter)
Get maximum and minimum value of normed data.
Definition CD_DataOps.cpp:1879
static void multiply(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs)
Multiply data holder by another data holder.
Definition CD_DataOps.cpp:2246
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:820
static void kappaScale(EBAMRCellData &a_data, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter) noexcept
Scale data by volume fraction.
Definition CD_DataOps.cpp:2142
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 dotProduct(MFAMRCellData &a_result, const MFAMRCellData &a_data1, const MFAMRCellData &a_data2)
Compote the cell-wise dot product between two data holders.
Definition CD_DataOps.cpp:537
static void squareRoot(EBAMRFluxData &a_lhs, Vector< RefCountedPtr< LayoutData< std::array< FaceIterator, SpaceDim > > > > &a_faceIter)
Compute the square root of the input data.
Definition CD_DataOps.cpp:3384
static void setCoveredValue(EBAMRCellData &a_lhs, const EBAMRCellData &a_coveredMask, const int a_comp, const Real a_value)
Set value in covered cells. Does specified component.
Definition CD_DataOps.cpp:2655
static void compute(EBAMRCellData &a_data, const std::function< Real(const Real a_cellValue)> &a_func, const Vector< RefCountedPtr< LayoutData< VoFIterator > > > &a_vofIter) noexcept
Compute a new value given the old cell value.
Definition CD_DataOps.cpp:482
static void copy(MFAMRCellData &a_dst, const MFAMRCellData &a_src)
Copy data from one data holder to another.
Definition CD_DataOps.cpp:1201
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:148
static void multiplyScalar(EBAMRCellData &a_lhs, const EBAMRCellData &a_rhs)
Multiply data holder by another data holder.
Definition CD_DataOps.cpp:2341
Type
Type of interpolation methods supported. PWC = Piecewise constant, ignoring the embedded boundary....
Definition CD_EBCoarseToFineInterp.H:43
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
void clearParticles()
Drop all valid particles on every level (keeps each leaf's arena capacity).
Definition CD_ParticleContainer.H:442
unsigned long long getNumberOfValidParticlesGlobal() const
Number of valid particles across all ranks.
Definition CD_ParticleContainer.H:543
void remap()
Redistribute every valid particle to the patch/level/rank that owns its cell.
Definition CD_ParticleContainerImplem.H:494
AMRParticlesSoA< P, Traits > & getParticles()
The valid particles on all levels.
Definition CD_ParticleContainer.H:317
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 copyDestructive(ParticleContainer< P, Traits > &a_dst, ParticleContainer< P, Traits > &a_src) noexcept
Move all particles from a_src into a_dst (per leaf), emptying a_src. SoA overload.
Definition CD_ParticleOpsImplem.H:278
static bool domainIntersection(const RealVect &a_oldPos, const RealVect &a_newPos, const RealVect &a_probLo, const RealVect &a_probHi, Real &a_s)
Compute the intersection point between a particle path and a domain side.
Definition CD_ParticleOpsImplem.H:126
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:955
double * weightColumn() noexcept
Raw weight column (double*).
Definition CD_ParticleSoA.H:1160
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:1137
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:1033
void appendParticle(const ParticleSoA &a_src, const std::size_t a_index)
Append a single particle (all columns, incl. id/rank) copied from another container.
Definition CD_ParticleSoAImplem.H:172
CdrSpecies subclass for use with DischargeInceptionStepper.
Definition CD_DischargeInceptionSpecies.H:30
TimeStepper for evaluating the streamer inception criterion in static or transient electric fields.
Definition CD_DischargeInceptionStepper.H:95
virtual const std::function< Real(const Real &E, const RealVect &x)> & getAlpha() const noexcept
Get ionization coefficient.
Definition CD_DischargeInceptionStepperImplem.H:2064
void parseRuntimeOptions() override
Parse runtime options.
Definition CD_DischargeInceptionStepperImplem.H:389
virtual const EBAMRCellData * getElectricField() const noexcept
Get the electric field.
Definition CD_DischargeInceptionStepperImplem.H:5564
void parseMode() noexcept
Parse simulation mode.
Definition CD_DischargeInceptionStepperImplem.H:419
void computeIonVelocity(const Real &a_voltage) noexcept
Set the negative ion velocity. Note.
Definition CD_DischargeInceptionStepperImplem.H:5258
virtual void setIonMobility(const std::function< Real(const Real E)> &a_mobility) noexcept
Set the negative ion mobility (field-dependent)
Definition CD_DischargeInceptionStepperImplem.H:2015
virtual void writeReportStationary() const noexcept
Print report to the terminal.
Definition CD_DischargeInceptionStepperImplem.H:5027
virtual Real advance(const Real a_dt) override
Advancement method. Swaps between various kernels.
Definition CD_DischargeInceptionStepperImplem.H:1602
virtual void townsendTrackTrapezoidal(const Real &a_voltage) noexcept
Track particles (positive ions) using a trapezoidal rule and check if the collide with a cathode.
Definition CD_DischargeInceptionStepperImplem.H:3426
virtual void seedUniformParticles() noexcept
Distribute particles in every grid cell.
Definition CD_DischargeInceptionStepperImplem.H:2137
virtual void resetTracerParticles() noexcept
Reset particles.
Definition CD_DischargeInceptionStepperImplem.H:3785
virtual void setIonDensity(const std::function< Real(const RealVect x)> &a_density) noexcept
Set the negative ion density.
Definition CD_DischargeInceptionStepperImplem.H:2003
virtual void computeCriticalVolumeStationary() noexcept
Compute the critical volume of the K values for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4569
virtual void writePlotDataStationary(LevelData< EBCellFAB > &a_output, int &a_icomp, const std::string &a_outputRealm, const int a_level) const noexcept
Write plot data for the 'stationary' mode.
Definition CD_DischargeInceptionStepperImplem.H:1118
virtual void postRegrid() override
Perform post-regrid operations.
Definition CD_DischargeInceptionStepperImplem.H:1954
virtual void inceptionIntegrateTrapezoidal(const Real &a_voltage) noexcept
K integral: Add integration parts after particles move.
Definition CD_DischargeInceptionStepperImplem.H:2822
virtual void setBackgroundRate(const std::function< Real(const Real &E, const RealVect &x)> &a_backgroundRate) noexcept
Set the background ionization rate (e.g. from cosmic radiation etc).
Definition CD_DischargeInceptionStepperImplem.H:2078
void computeIonDiffusion(const Real &a_voltage) noexcept
Set the negative ion diffusion coefficient.
Definition CD_DischargeInceptionStepperImplem.H:5324
virtual std::pair< Real, RealVect > computeMinimumInceptionVoltage(const EBAMRCellData &a_Uinc) const noexcept
Compute the minimum inception voltage and the starting electron position.
Definition CD_DischargeInceptionStepperImplem.H:4494
virtual void computeTownsendCriterionStationary() noexcept
Solve for the Townsend criterion for each particle in each voltage.
Definition CD_DischargeInceptionStepperImplem.H:3113
virtual void synchronizeSolverTimes(const int a_step, const Real a_time, const Real a_dt) override
Synchronize solver times and time steps.
Definition CD_DischargeInceptionStepperImplem.H:1800
virtual void inceptionIntegrateEuler(const Real &a_voltage) noexcept
Integrate the inception integral using the Euler rule.
Definition CD_DischargeInceptionStepperImplem.H:2632
virtual void computeInceptionVoltageVolume() noexcept
Interpolate between K values to find voltage giving K_inception and store values in m_inceptionVoltag...
Definition CD_DischargeInceptionStepperImplem.H:4233
virtual Real computeIonizationVolumeTransient(const Real &a_voltage) const noexcept
Compute the ionization volume for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4945
virtual void computeFieldEmission(EBAMRCellData &a_emissionRate, const Real &a_voltage) const noexcept
Compute field emission rates.
Definition CD_DischargeInceptionStepperImplem.H:4087
virtual int getNumberOfPlotVariables() const override
Get the number of plot variables for this time stepper.
Definition CD_DischargeInceptionStepperImplem.H:672
virtual Real computeCriticalVolumeTransient() const noexcept
Compute the critical volume of the K values for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4660
void registerOperators() override
Register operators.
Definition CD_DischargeInceptionStepperImplem.H:360
virtual void getMaxValueAndLocation(Real &a_maxVal, RealVect &a_maxPos, const EBAMRCellData &a_data) const noexcept
Get the maximum value and location corresponding to the maximum value in the input data holder.
Definition CD_DischargeInceptionStepperImplem.H:5434
virtual void evaluateFunction(EBAMRCellData &a_data, const Real &a_voltage, const std::function< Real(const Real E, const RealVect x)> &a_func) const noexcept
Evaluate a function f = f(E, x) in a volume.
Definition CD_DischargeInceptionStepperImplem.H:4146
void allocate() override
Allocate storage for solvers and time stepper.
Definition CD_DischargeInceptionStepperImplem.H:173
virtual Vector< std::string > getTransientPlotVariableNames() const noexcept
Get plot variable names for transient mode.
Definition CD_DischargeInceptionStepperImplem.H:1010
virtual Mode getMode() const noexcept
Get the solver mode.
Definition CD_DischargeInceptionStepperImplem.H:2130
void parseInceptionAlgorithm() noexcept
Parse the inception algorithm.
Definition CD_DischargeInceptionStepperImplem.H:478
virtual ~DischargeInceptionStepper()
Destructor.
Definition CD_DischargeInceptionStepperImplem.H:113
void solvePoisson() noexcept
Solve the Poisson equation.
Definition CD_DischargeInceptionStepperImplem.H:251
virtual void setAlpha(const std::function< Real(const Real &E, const RealVect &x)> &a_alpha) noexcept
Set the ionization coefficient.
Definition CD_DischargeInceptionStepperImplem.H:2039
virtual void computeInceptionIntegral(EBAMRCellData &a_inceptionIntegral, const Real a_voltage) noexcept
Compute the inception integral for the input voltage.
Definition CD_DischargeInceptionStepperImplem.H:2438
virtual void regrid(const int a_lmin, const int a_oldFinestLevel, const int a_newFinestLevel) override
Time stepper regrid method.
Definition CD_DischargeInceptionStepperImplem.H:1891
bool particleOutsideGrid(const RealVect &a_pos, const RealVect &a_probLo, const RealVect &a_probHi) const noexcept
Check if particle is outside grid boundaries.
Definition CD_DischargeInceptionStepperImplem.H:5218
virtual void writeReportTransient() const noexcept
Print report to the terminal.
Definition CD_DischargeInceptionStepperImplem.H:5138
virtual void rewindTracerParticles() noexcept
Move particles back to their original position.
Definition CD_DischargeInceptionStepperImplem.H:3747
virtual void townsendTrackEuler(const Real &a_voltage) noexcept
Track particles (positive ions) using an Euler rule and check if the collide with a cathode.
Definition CD_DischargeInceptionStepperImplem.H:3303
void superposition(EBAMRCellData &a_sumField, const MFAMRCellData &a_inhomogeneousField, const MFAMRCellData &a_homogeneousField, const Real a_voltage) const noexcept
Calculate the total electric field = inhomogeneous + V * homogeneous.
Definition CD_DischargeInceptionStepperImplem.H:5404
void parseOptions()
Parse options.
Definition CD_DischargeInceptionStepperImplem.H:374
void setupSolvers() override
Instantiate the tracer particle solver.
Definition CD_DischargeInceptionStepperImplem.H:123
virtual void setEta(const std::function< Real(const Real &E, const RealVect &x)> &a_eta) noexcept
Set the attachment coefficient.
Definition CD_DischargeInceptionStepperImplem.H:2052
virtual Vector< std::string > getStationaryPlotVariableNames() const noexcept
Get plot variable names for stationary mode.
Definition CD_DischargeInceptionStepperImplem.H:865
virtual void setDetachmentRate(const std::function< Real(const Real &E, const RealVect &x)> &a_detachmentRate) noexcept
Set the detachment rate for negative ions.
Definition CD_DischargeInceptionStepperImplem.H:2091
virtual void computeTownsendCriterionTransient(const Real &a_voltage) noexcept
Solve for the Townsend criterion for each particle in each voltage.
Definition CD_DischargeInceptionStepperImplem.H:3247
virtual void computeIonizationVolumeStationary() noexcept
Compute the ionization volume for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4847
virtual void writePlotDataTransient(LevelData< EBCellFAB > &a_output, int &a_icomp, const std::string &a_outputRealm, const int a_level) const noexcept
Write plot data for the 'transient' mode.
Definition CD_DischargeInceptionStepperImplem.H:1332
virtual void computeFieldEmissionStationary() noexcept
Compute field emission rates.
Definition CD_DischargeInceptionStepperImplem.H:3987
void parseOutput() noexcept
Parse output settings.
Definition CD_DischargeInceptionStepperImplem.H:464
virtual void computeInceptionIntegralStationary() noexcept
Solve streamer inception integral for each particle in each voltage and store K values in m_inception...
Definition CD_DischargeInceptionStepperImplem.H:2493
virtual Real computeCriticalAreaTransient() const noexcept
Compute the critical area of the K values for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4796
virtual Real getCriticalField() const noexcept
Get the breakdown field.
Definition CD_DischargeInceptionStepperImplem.H:5573
virtual Vector< std::string > getPlotVariableNames() const override
Get plot variable names.
Definition CD_DischargeInceptionStepperImplem.H:809
virtual void computeDetachmentStationary() noexcept
Compute the detachment ionization rate for all voltages.
Definition CD_DischargeInceptionStepperImplem.H:3899
virtual void writeData(LevelData< EBCellFAB > &a_output, int &a_comp, const EBAMRCellData &a_data, const std::string a_outputRealm, const int a_level, const bool a_interpToCentroids, const bool a_interpGhost) const noexcept
Write data to output. Convenience function used for IO.
Definition CD_DischargeInceptionStepperImplem.H:5506
void parseVoltages() noexcept
Parse voltage levels.
Definition CD_DischargeInceptionStepperImplem.H:444
virtual void interpolateGradAlphaToParticles() noexcept
Interpolate alpha/|grad(alpha)| onto some scratch particle storage.
Definition CD_DischargeInceptionStepperImplem.H:2420
virtual void writePlotData(LevelData< EBCellFAB > &a_output, int &a_icomp, const std::string &a_outputRealm, const int a_level) const override
Write plot data to output holder.
Definition CD_DischargeInceptionStepperImplem.H:1075
virtual void setIonDiffusion(const std::function< Real(const Real E)> &a_diffCo) noexcept
Set the negative ion diffusion coefficient (field-dependent)
Definition CD_DischargeInceptionStepperImplem.H:2027
void postInitialize() override
Perform any post-initialization steps.
Definition CD_DischargeInceptionStepperImplem.H:2338
bool particleInsideEB(const RealVect &a_pos) const noexcept
Check if particle is inside electrode.
Definition CD_DischargeInceptionStepperImplem.H:5242
virtual void setSigma(const std::function< Real(const RealVect &x)> &a_sigma) noexcept
Set surface charge distribution.
Definition CD_DischargeInceptionStepperImplem.H:1991
void parseTransportAlgorithm() noexcept
Parse the transport algorithm.
Definition CD_DischargeInceptionStepperImplem.H:535
virtual void seedIonizationParticles(const Real a_voltage) noexcept
Add particles to every cell where alpha - eta > 0.0.
Definition CD_DischargeInceptionStepperImplem.H:2210
DischargeInceptionStepper()
Default constructor.
Definition CD_DischargeInceptionStepperImplem.H:39
void parsePlotVariables() noexcept
Parse plot variables.
Definition CD_DischargeInceptionStepperImplem.H:575
void initialData() override
Fill problem with initial data.
Definition CD_DischargeInceptionStepperImplem.H:239
virtual const std::function< Real(const Real &E, const RealVect &x)> & getEta() const noexcept
Get attachment coefficient.
Definition CD_DischargeInceptionStepperImplem.H:2071
virtual void setSecondaryEmission(const std::function< Real(const Real &E, const RealVect &x)> &a_coeff) noexcept
Set the secondary emission coefficient.
Definition CD_DischargeInceptionStepperImplem.H:2117
virtual Real computeDt() override
Compute a time step to be used by Driver.
Definition CD_DischargeInceptionStepperImplem.H:1515
virtual void preRegrid(const int a_lmin, const int a_oldFinestLevel) override
Perform pre-regrid operations.
Definition CD_DischargeInceptionStepperImplem.H:1871
virtual void printStepReport() override
Print a step report. Used in transient simulations.
Definition CD_DischargeInceptionStepperImplem.H:1818
virtual void setRho(const std::function< Real(const RealVect &x)> &a_rho) noexcept
Set space charge distribution.
Definition CD_DischargeInceptionStepperImplem.H:1979
void parseVerbosity() noexcept
Parse class verbosity.
Definition CD_DischargeInceptionStepperImplem.H:404
void registerRealms() override
Register realms. Primal is the only realm we need.
Definition CD_DischargeInceptionStepperImplem.H:348
virtual void computeBackgroundIonizationStationary() noexcept
Compute the background ionization rate for all voltages.
Definition CD_DischargeInceptionStepperImplem.H:3817
virtual void computeInceptionIntegralTransient(const Real &a_voltage) noexcept
Solve streamer inception integral.
Definition CD_DischargeInceptionStepperImplem.H:2597
virtual void setVoltageCurve(const std::function< Real(const Real &a_time)> &a_voltageCurve) noexcept
Set the voltage curve (used for transient mode)
Definition CD_DischargeInceptionStepperImplem.H:1967
virtual void computeCriticalAreaStationary() noexcept
Compute the critical area of the K values for each voltage.
Definition CD_DischargeInceptionStepperImplem.H:4724
virtual void advanceIons(const Real a_dt) noexcept
Advance negative ions.
Definition CD_DischargeInceptionStepperImplem.H:1714
virtual Real computeRdot(const Real &a_voltage) const noexcept
Compute integral_Vcr(done/dt * (1 - eta/alpha) dV)
Definition CD_DischargeInceptionStepperImplem.H:3643
virtual void setFieldEmission(const std::function< Real(const Real &E, const RealVect &x)> &a_currentDensity) noexcept
Set the field emission current.
Definition CD_DischargeInceptionStepperImplem.H:2104
static const std::string Primal
Identifier for perimal realm.
Definition CD_Realm.H:44
Class which is used for run-time monitoring of events.
Definition CD_Timer.H:32
void startEvent(const std::string &a_event) noexcept
Start an event.
Definition CD_TimerImplem.H:60
void eventReport(std::ostream &a_outputStream, const bool a_localReportOnly=false) const noexcept
Print all timed events to cout.
Definition CD_TimerImplem.H:170
void stopEvent(const std::string &a_event) noexcept
Stop an event.
Definition CD_TimerImplem.H:89
Base class for a tracer particle solver. This solver can advance particles in a pre-defined velocity ...
Definition CD_TracerParticleSolver.H:39
ALWAYS_INLINE void loop(const Box &a_computeBox, Functor &&kernel)
Launch a C++ kernel over a regular grid with compile-time per-dimension strides.
Definition CD_BoxLoopsImplem.H:39
RealVect position(Location::Cell a_location, const VolIndex &a_vof, const EBISBox &a_ebisbox, const Real &a_dx)
Compute the position (ignoring the "origin) of a Vof.
Definition CD_LocationImplem.H:21
Real max(const Real &a_input) noexcept
Get the maximum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition CD_ParallelOpsImplem.H:177
Real min(const Real &a_input) noexcept
Get the minimum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition CD_ParallelOpsImplem.H:59
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354
ALWAYS_INLINE void loop(const ParticleSoA< P, Traits > &a_soa, Functor &&a_kernel)
Launch a kernel over every particle in a ParticleSoA, decorating the loop with CD_PRAGMA_SIMD.
Definition CD_ParticleLoops.H:87
Real brentSolve(const Real a_point1, const Real a_point2, const std::function< Real(const Real x)> &a_func) noexcept
Compute the root of a function between two points. This is a 1D problem.
Definition CD_PolyUtils.cpp:169
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