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