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