Cantera  4.0.0a2
Loading...
Searching...
No Matches
PlasmaPhase.cpp
Go to the documentation of this file.
1//! @file PlasmaPhase.cpp
2
3// This file is part of Cantera. See License.txt in the top-level directory or
4// at https://cantera.org/license.txt for license and copyright information.
5
8#include <boost/math/special_functions/gamma.hpp>
10#include "cantera/base/global.h"
11#include "cantera/numerics/eigen_dense.h"
15#include <boost/polymorphic_pointer_cast.hpp>
17
18namespace Cantera {
19
20namespace {
21 const double gamma = sqrt(2 * ElectronCharge / ElectronMass);
22}
23
24PlasmaPhase::PlasmaPhase(const string& inputFile, const string& id_)
25{
26 // Initialize the Boltzmann solver and default energy grid before reading
27 // input so that isotropic/discretized EEDF setters have a valid grid.
28 m_eedfSolver = make_unique<EEDFTwoTermApproximation>(this);
29
30 double kTe_max = 60;
31 size_t nGridCells = 301;
32 m_nPoints = nGridCells + 1;
33 m_eedfSolver->setLinearGrid(kTe_max, nGridCells);
36
37 // initial electron temperature; may be updated by input file data
39
40 initThermoFile(inputFile, id_);
41}
42
43PlasmaPhase::~PlasmaPhase()
44{
45 if (shared_ptr<Solution> soln = m_soln.lock()) {
46 soln->removeChangedCallback(this);
47 soln->kinetics()->removeReactionAddedCallback(this);
48 }
49 for (size_t k = 0; k < nCollisions(); k++) {
50 // remove callback
51 m_collisions[k]->removeSetRateCallback(this);
52 }
53}
54
56{
58
59 // Check if there is an electron species in the phase.
61 throw CanteraError("PlasmaPhase::initThermo",
62 "No electron species found.");
63 }
64}
65
67{
68 // Update the heavy species thermodynamic properties
69 // before updating the electron species properties.
71 static const int cacheId = m_cache.getId();
72 CachedScalar cached = m_cache.getScalar(cacheId);
73 double tempNow = temperature();
74 double electronTempNow = electronTemperature();
75 size_t k = m_electronSpeciesIndex;
76 // If the electron temperature has changed since the last time these
77 // properties were computed, recompute them.
78 if (cached.state1 != tempNow || cached.state2 != electronTempNow) {
79 // Evaluate the electron species thermodynamic properties
80 // at the electron temperature.
82 m_cp0_R[k], m_h0_RT[k], m_s0_R[k]);
83 cached.state1 = tempNow;
84 cached.state2 = electronTempNow;
85
86 // Update the electron Gibbs functions, with the electron temperature.
87 m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k];
88 }
89}
90
91// ================================================================= //
92// Overridden from IdealGasPhase or ThermoPhase //
93// ================================================================= //
94
95bool PlasmaPhase::addSpecies(shared_ptr<Species> spec)
96{
97 bool added = IdealGasPhase::addSpecies(spec);
98 size_t k = m_kk - 1;
99
100 if ((spec->name == "e" || spec->name == "Electron") ||
101 (spec->composition.find("E") != spec->composition.end() &&
102 spec->composition.size() == 1 &&
103 spec->composition["E"] == 1)) {
106 } else {
107 throw CanteraError("PlasmaPhase::addSpecies",
108 "Cannot add species, {}. "
109 "Only one electron species is allowed.", spec->name);
110 }
111 }
112 return added;
113}
114
115void PlasmaPhase::setSolution(std::weak_ptr<Solution> soln) {
117 // Register callback function to be executed
118 // when the thermo or kinetics object changed.
119 if (shared_ptr<Solution> soln = m_soln.lock()) {
120 soln->registerChangedCallback(this, [&]() {
122 });
123 }
124}
125
126void PlasmaPhase::getParameters(AnyMap& phaseNode) const
127{
129 AnyMap eedf;
130 eedf["type"] = m_distributionType;
131 vector<double> levels(m_nPoints);
132 Eigen::Map<Eigen::ArrayXd>(levels.data(), m_nPoints) = m_electronEnergyLevels;
133 eedf["energy-levels"] = levels;
134 if (m_distributionType == "isotropic") {
135 eedf["shape-factor"] = m_isotropicShapeFactor;
136 eedf["mean-electron-energy"].setQuantity(meanElectronEnergy(), "eV");
137 } else if (m_distributionType == "discretized") {
138 vector<double> dist(m_nPoints);
139 Eigen::Map<Eigen::ArrayXd>(dist.data(), m_nPoints) = m_electronEnergyDist;
140 eedf["distribution"] = dist;
141 eedf["normalize"] = m_do_normalizeElectronEnergyDist;
142 }
143 phaseNode["electron-energy-distribution"] = std::move(eedf);
144}
145
147{
148 const string routineName = "PlasmaPhase::setElectronEnergyDistributionParameters";
149 if (!eedf.hasKey("type")) {
150 throw InputFileError(routineName, eedf,
151 "The electron energy distribution mapping requires the key 'type'.");
152 }
153
154 m_distributionType = eedf["type"].asString();
155 if (m_distributionType == "isotropic") {
156 if (eedf.hasKey("shape-factor")) {
157 setIsotropicShapeFactor(eedf["shape-factor"].asDouble());
158 } else {
159 throw InputFileError(routineName, eedf,
160 "isotropic type requires shape-factor key.");
161 }
162 if (eedf.hasKey("mean-electron-energy")) {
163 double energy = eedf.convert("mean-electron-energy", "eV");
164 setMeanElectronEnergy(energy);
165 } else {
166 throw InputFileError(routineName, eedf,
167 "isotropic type requires mean-electron-energy key.");
168 }
169 if (eedf.hasKey("energy-levels")) {
170 auto levels = eedf["energy-levels"].asVector<double>();
172 }
174 } else if (m_distributionType == "discretized") {
175 if (!eedf.hasKey("energy-levels")) {
176 throw InputFileError(routineName, eedf,
177 "Cannot find key energy-levels.");
178 }
179 if (!eedf.hasKey("distribution")) {
180 throw InputFileError(routineName, eedf,
181 "Cannot find key distribution.");
182 }
183 if (eedf.hasKey("normalize")) {
184 enableNormalizeElectronEnergyDist(eedf["normalize"].asBool());
185 }
186 auto levels = eedf["energy-levels"].asVector<double>();
187 auto distribution = eedf["distribution"].asVector<double>(levels.size());
188 setDiscretizedElectronEnergyDist(levels, distribution);
189 } else if (m_distributionType == "Boltzmann-two-term") {
190 if (eedf.hasKey("energy-levels")) {
191 auto levels = eedf["energy-levels"].asVector<double>();
192 m_eedfSolver->setCustomGrid(levels);
193 m_eedfSolver->enableGridAdaptation(false);
194 m_nPoints = levels.size();
195 } else {
196 if (!eedf.hasKey("initial-max-energy-level")) {
197 throw InputFileError(routineName, eedf,
198 "Boltzmann-two-term requires either "
199 "'energy-levels' or 'initial-max-energy-level'.");
200 }
201
202 if (!eedf.hasKey("grid-cell-count")) {
203 throw InputFileError(routineName, eedf,
204 "Boltzmann-two-term requires either 'energy-levels' "
205 "or 'grid-cell-count'.");
206 }
207
208 double initialMaxEnergy = eedf["initial-max-energy-level"].asDouble();
209 size_t nGridCells = static_cast<size_t>(eedf["grid-cell-count"].asInt());
210
211 if (!std::isfinite(initialMaxEnergy) || initialMaxEnergy <= 0.0) {
212 throw InputFileError(routineName, eedf,
213 "initial-max-energy-level must be finite and greater than zero.");
214 }
215
216 if (nGridCells == 0) {
217 throw InputFileError(routineName, eedf,
218 "grid-cell-count must be greater than zero.");
219 }
220
221 string energyLevelsDistribution =
222 eedf.getString("energy-level-spacing", "linear");
223
224 m_eedfSolver->setInitialGridParameters(
225 initialMaxEnergy, nGridCells, energyLevelsDistribution);
226
227 if (energyLevelsDistribution == "linear") {
228 m_eedfSolver->setLinearGrid(initialMaxEnergy, nGridCells);
229 } else if (energyLevelsDistribution == "quadratic") {
230 m_eedfSolver->setQuadraticGrid(initialMaxEnergy, nGridCells);
231 } else if (energyLevelsDistribution == "geometric") {
232 if (eedf.hasKey("geometric-grid-ratio")) {
233 double ratio = eedf["geometric-grid-ratio"].asDouble();
234 if (!std::isfinite(ratio) || ratio <= 1.0) {
235 throw InputFileError(routineName, eedf,
236 "geometric-grid-ratio must be finite and greater than 1.0.");
237 }
238 m_eedfSolver->setGeometricGrid(initialMaxEnergy, nGridCells, ratio);
239 } else {
240 m_eedfSolver->setGeometricGrid(initialMaxEnergy, nGridCells);
241 }
242 } else {
243 throw InputFileError(routineName, eedf,
244 "energy-level-spacing should be linear, quadratic or geometric.");
245 }
246
247 if (eedf.hasKey("energy-grid-adaptation")) {
248 const AnyMap adapt = eedf["energy-grid-adaptation"].as<AnyMap>();
249 bool enabled = adapt.getBool("enabled", true);
250 bool maxwellianReset = adapt.getBool("Maxwellian-reset", true);
251 double minDecayDecades = adapt.getDouble("min-decay-decades", 10.0);
252 double maxDecayDecades = adapt.getDouble("max-decay-decades", 12.0);
253 double updateFactor = adapt.getDouble("update-factor", 0.1);
254 size_t maxIterations = adapt.getInt("max-iterations", 1000);
255 m_eedfSolver->enableGridAdaptation(enabled);
256 m_eedfSolver->setGridAdaptationParameters(
257 minDecayDecades, maxDecayDecades, updateFactor, maxIterations,
258 maxwellianReset);
259 } else {
260 m_eedfSolver->enableGridAdaptation(false);
261 }
262
263 m_nPoints = nGridCells + 1;
264 }
265
266 if (eedf.hasKey("reduced-field-threshold-before-Maxwellian")) {
267 double maxwellianThreshold =
268 eedf.convert("reduced-field-threshold-before-Maxwellian", "Td");
269 if (!std::isfinite(maxwellianThreshold) || maxwellianThreshold < 0.0) {
270 throw InputFileError(routineName, eedf,
271 "reduced-field-threshold-before-Maxwellian must be finite "
272 "and non-negative.");
273 }
274 // The input to this function is expected to be in Townsend.
275 m_eedfSolver->setReducedElectricFieldThresholdForMaxwellian(
276 maxwellianThreshold);
277 }
278
279 auto levels = m_eedfSolver->getGridEdge();
280 m_nPoints = levels.size();
282 m_electronEnergyDist.setZero(static_cast<Eigen::Index>(m_nPoints));
283
286 } else {
287 throw InputFileError(routineName, eedf,
288 "Unknown electron energy distribution type '{}'. Supported types are "
289 "'isotropic', 'discretized', and 'Boltzmann-two-term'.",
291 }
292}
293
294void PlasmaPhase::setParameters(const AnyMap& phaseNode, const AnyMap& rootNode)
295{
296 IdealGasPhase::setParameters(phaseNode, rootNode);
297 if (phaseNode.hasKey("electron-energy-distribution")) {
298 const AnyMap eedf = phaseNode["electron-energy-distribution"].as<AnyMap>();
300 }
301
302 if (rootNode.hasKey("electron-collisions")) {
303 for (const auto& item : rootNode["electron-collisions"].asVector<AnyMap>()) {
304 auto rate = make_shared<ElectronCollisionPlasmaRate>(item);
305 Composition reactants, products;
306 reactants[item["target"].asString()] = 1;
307 reactants[electronSpeciesName()] = 1;
308 if (item.hasKey("product")) {
309 products[item["product"].asString()] = 1;
310 } else {
311 products[item["target"].asString()] = 1;
312 }
313 products[electronSpeciesName()] = 1;
314 if (rate->kind() == "ionization") {
315 products[electronSpeciesName()] += 1;
316 } else if (rate->kind() == "attachment") {
317 products[electronSpeciesName()] -= 1;
318 }
319 auto R = make_shared<Reaction>(reactants, products, rate);
320 addCollision(R);
321 }
322 }
323}
324
325// ================================================================= //
326// Electron Energy Distribution Functions //
327// ================================================================= //
328
330{
331 if (m_distributionType == "discretized") {
332 throw CanteraError("PlasmaPhase::updateElectronEnergyDistribution",
333 "Invalid for discretized electron energy distribution.");
334 } else if (m_distributionType == "isotropic") {
336 } else if (m_distributionType == "Boltzmann-two-term") {
337 auto ierr = m_eedfSolver->calculateDistributionFunction();
338 if (ierr == 0) {
343 } else {
344 throw CanteraError("PlasmaPhase::updateElectronEnergyDistribution",
345 "Call to calculateDistributionFunction failed.");
346 }
347 } else {
348 throw CanteraError("PlasmaPhase::updateElectronEnergyDistribution",
349 "Unknown method '{}' for determining EEDF", m_distributionType);
350 }
353}
354
356 Eigen::ArrayXd eps32 = m_electronEnergyLevels.pow(3./2.);
357 double norm = 2./3. * numericalQuadrature(m_quadratureMethod,
358 m_electronEnergyDist, eps32);
359 if (norm < 0.0) {
360 throw CanteraError("PlasmaPhase::normalizeElectronEnergyDistribution",
361 "The norm is negative. This might be caused by bad "
362 "electron energy distribution");
363 }
364 m_electronEnergyDist /= norm;
365}
366
368{
369 if (type == "discretized" ||
370 type == "isotropic" ||
371 type == "Boltzmann-two-term") {
373 } else {
374 throw CanteraError("PlasmaPhase::setElectronEnergyDistributionType",
375 "Unknown type for electron energy distribution.");
376 }
377}
378
380{
382 double x = m_isotropicShapeFactor;
383 double gamma1 = boost::math::tgamma(3.0 / 2.0 / x);
384 double gamma2 = boost::math::tgamma(5.0 / 2.0 / x);
385 double c1 = x * std::pow(gamma2, 1.5) / std::pow(gamma1, 2.5);
386 double c2 = std::pow(gamma2 / gamma1, x);
388 c1 / std::pow(meanElectronEnergy(), 1.5) *
389 (-c2 * (m_electronEnergyLevels /
390 meanElectronEnergy()).pow(x)).exp();
392}
393
395 if (Te < 0.0) {
396 throw CanteraError("PlasmaPhase::setElectronTemperature",
397 "Electron temperature cannot be negative.");
398 }
399 m_electronTemp = Te;
401}
402
404{
406
407 if (!m_inEquilibrate) {
408 m_inEquilibrate = true;
409 // Remember current Te and lock Te -> T for the duration
412 }
413}
414
416{
417 if (m_inEquilibrate) {
418 // Restore Te to the pre-equilibrate value
420 m_inEquilibrate = false;
421 }
422
424}
425
427 setElectronTemperature(2.0 / 3.0 * energy * ElectronCharge / Boltzmann);
428}
429
430void PlasmaPhase::setElectronEnergyLevels(span<const double> levels)
431{
432 m_nPoints = levels.size();
433 m_electronEnergyLevels = Eigen::Map<const Eigen::ArrayXd>(levels.data(), m_nPoints);
437}
438
440{
441 m_distNum++;
442}
443
445{
446 m_levelNum++;
447 // Cross sections are interpolated on the energy levels
448 if (m_collisions.size() > 0) {
449 for (shared_ptr<Reaction> collision : m_collisions) {
450 const auto& rate = boost::polymorphic_pointer_downcast
453 }
454 }
455}
456
458{
459 Eigen::ArrayXd h = m_electronEnergyLevels.tail(m_nPoints - 1) -
461 if (m_electronEnergyLevels[0] < 0.0 || (h <= 0.0).any()) {
462 throw CanteraError("PlasmaPhase::checkElectronEnergyLevels",
463 "Values of electron energy levels need to be positive and "
464 "monotonically increasing.");
465 }
466}
467
469{
470 Eigen::ArrayXd h = m_electronEnergyLevels.tail(m_nPoints - 1) -
472 if ((m_electronEnergyDist < 0.0).any()) {
473 throw CanteraError("PlasmaPhase::checkElectronEnergyDistribution",
474 "Values of electron energy distribution cannot be negative.");
475 }
476 if (m_electronEnergyDist[m_nPoints - 1] > 0.01) {
477 warn_user("PlasmaPhase::checkElectronEnergyDistribution",
478 "The value of the last element of electron energy distribution exceed 0.01. "
479 "This indicates that the value of electron energy level is not high enough "
480 "to contain the isotropic distribution at mean electron energy of "
481 "{} eV", meanElectronEnergy());
482 }
483}
484
486 span<const double> dist)
487{
488 m_distributionType = "discretized";
489 m_nPoints = levels.size();
495 }
501}
502
504{
505 // calculate mean electron energy and electron temperature
506 Eigen::ArrayXd eps52 = m_electronEnergyLevels.pow(5./2.);
507 double epsilon_m = 2.0 / 5.0 * numericalQuadrature(m_quadratureMethod,
508 m_electronEnergyDist, eps52);
509 if (epsilon_m < 0.0 && m_quadratureMethod == "simpson") {
510 // try trapezoidal method
511 epsilon_m = 2.0 / 5.0 * numericalQuadrature(
512 "trapezoidal", m_electronEnergyDist, eps52);
513 }
514
515 if (epsilon_m < 0.0) {
516 throw CanteraError("PlasmaPhase::updateElectronTemperatureFromEnergyDist",
517 "The electron energy distribution produces negative electron temperature.");
518 }
519
520 m_electronTemp = 2.0 / 3.0 * epsilon_m * ElectronCharge / Boltzmann;
521}
522
524 m_isotropicShapeFactor = x;
526}
527
529{
530 if (shared_ptr<Solution> soln = m_soln.lock()) {
531 shared_ptr<Kinetics> kin = soln->kinetics();
532 if (!kin) {
533 return;
534 }
535
536 // add collision from the initial list of reactions. Only add reactions we
537 // haven't seen before
538 set<Reaction*> existing;
539 for (auto& R : m_collisions) {
540 existing.insert(R.get());
541 }
542 for (size_t i = 0; i < kin->nReactions(); i++) {
543 shared_ptr<Reaction> R = kin->reaction(i);
544 if (R->rate()->type() != "electron-collision-plasma"
545 || existing.count(R.get())) {
546 continue;
547 }
548 addCollision(R);
549 }
550
551 // Register callback when reaction is added later.
552 // Modifying collision reactions is not supported.
553 kin->registerReactionAddedCallback(this, [this, kin]() {
554 size_t i = kin->nReactions() - 1;
555 if (kin->reaction(i)->type() == "electron-collision-plasma") {
556 addCollision(kin->reaction(i));
557 }
558 });
559 }
560}
561
562void PlasmaPhase::addCollision(shared_ptr<Reaction> collision)
563{
564 size_t i = nCollisions();
565
566 // setup callback to signal updating the cross-section-related
567 // parameters
568 collision->registerSetRateCallback(this, [this, i, collision]() {
569 m_interp_cs_ready[i] = false;
571 std::dynamic_pointer_cast<ElectronCollisionPlasmaRate>(collision->rate());
572 });
573
574 // Identify target species for electron-collision reactions
575 string target;
576 for (const auto& [name, _] : collision->reactants) {
577 // Reactants are expected to be electrons and the target species
578 if (name != electronSpeciesName()) {
579 m_targetSpeciesIndices.emplace_back(speciesIndex(name, true));
580 target = name;
581 break;
582 }
583 }
584 if (target.empty()) {
585 throw CanteraError("PlasmaPhase::addCollision", "Error identifying target for"
586 " collision with equation '{}'", collision->equation());
587 }
588
589 m_collisions.emplace_back(collision);
590 m_collisionRates.emplace_back(
591 std::dynamic_pointer_cast<ElectronCollisionPlasmaRate>(collision->rate()));
592 m_interp_cs_ready.emplace_back(false);
593
594 // resize parameters
597
598 // Set up data used by Boltzmann solver
599 auto& rate = *m_collisionRates.back();
600 string kind = m_collisionRates.back()->kind();
601
602 if ((kind == "effective" || kind == "elastic")) {
603 for (size_t k = 0; k < m_collisions.size() - 1; k++) {
604 if (m_collisions[k]->reactants == collision->reactants &&
605 (m_collisionRates[k]->kind() == "elastic" ||
606 m_collisionRates[k]->kind() == "effective") && !collision->duplicate)
607 {
608 throw CanteraError("PlasmaPhase::addCollision", "Phase already contains"
609 " an effective/elastic cross section for '{}'.", target);
610 }
611 }
612 m_kElastic.push_back(i);
613 } else {
614 m_kInelastic.push_back(i);
615 }
616
617 auto levels = rate.energyLevels();
618 m_energyLevels.emplace_back(levels.begin(), levels.end());
619 auto sections = rate.crossSections();
620 m_crossSections.emplace_back(sections.begin(), sections.end());
621 m_eedfSolver->setGridCache();
622}
623
625{
626 if (m_interp_cs_ready[i]) {
627 return false;
628 }
629 vector<double> levels(m_nPoints);
630 Eigen::Map<Eigen::ArrayXd>(levels.data(), m_nPoints) = m_electronEnergyLevels;
631 m_collisionRates[i]->updateInterpolatedCrossSection(levels);
632 m_interp_cs_ready[i] = true;
633 return true;
634}
635
637{
639 // Forward difference for the first point
643
644 // Central difference for the middle points
645 for (size_t i = 1; i < m_nPoints - 1; i++) {
649 (h1 * h1 - h0 * h0) * m_electronEnergyDist[i] -
650 h1 * h1 * m_electronEnergyDist[i-1]) /
651 (h1 * h0) / (h1 + h0);
652 }
653
654 // Backward difference for the last point
660}
661
663{
664 // cache of cross section plus distribution plus energy-level number
665 static const int cacheId = m_cache.getId();
666 CachedScalar last_stateNum = m_cache.getScalar(cacheId);
667
668 // combine the distribution and energy level number
669 int stateNum = m_distNum + m_levelNum;
670
671 vector<bool> interpChanged(m_collisions.size());
672 for (size_t i = 0; i < m_collisions.size(); i++) {
673 interpChanged[i] = updateInterpolatedCrossSection(i);
674 }
675
676 if (last_stateNum.validate(temperature(), stateNum)) {
677 // check each cross section, and only update coefficients that
678 // the interpolated cross sections change
679 for (size_t i = 0; i < m_collisions.size(); i++) {
680 if (interpChanged[i]) {
682 }
683 }
684 } else {
685 // update every coefficient if distribution, temperature,
686 // or energy levels change.
687 for (size_t i = 0; i < m_collisions.size(); i++) {
689 }
690 }
691}
692
694{
695 // @todo exclude attachment collisions
696 size_t k = m_targetSpeciesIndices[i];
697
698 // Map cross sections to Eigen::ArrayXd
699 auto cs_array = Eigen::Map<const Eigen::ArrayXd>(
700 m_collisionRates[i]->crossSectionInterpolated().data(),
701 m_collisionRates[i]->crossSectionInterpolated().size()
702 );
703
704 // Mass ratio calculation
705 double mass_ratio = ElectronMass / molecularWeight(k) * Avogadro;
706
707 // Calculate the rate using Simpson's rule or trapezoidal rule
708 Eigen::ArrayXd f0_plus = m_electronEnergyDist + Boltzmann * temperature() /
710 m_elasticElectronEnergyLossCoefficients[i] = 2.0 * mass_ratio * gamma *
712 m_quadratureMethod, 1.0 / 3.0 * f0_plus.cwiseProduct(cs_array),
713 m_electronEnergyLevels.pow(3.0));
714}
715
717{
718 if (m_electronEnergyDist.size() != m_nPoints
719 || m_electronEnergyDistDiff.size() != m_nPoints) {
720 throw CanteraError("PlasmaPhase::elasticPowerLoss:",
721 "EEDF not initialized");
722 }
723
725 // The elastic power loss includes the contributions from inelastic
726 // collisions (inelastic recoil effects).
727 double rate = 0.0;
728 for (size_t i = 0; i < nCollisions(); i++) {
731 }
732 const double q_elastic = Avogadro * Avogadro * ElectronCharge *
734
735 if (!std::isfinite(q_elastic)) {
736 throw CanteraError("PlasmaPhase::elasticPowerLoss:",
737 "Non-finite elastic power loss");
738 }
739
740 return q_elastic;
741}
742
744{
745 // Only implemented when using the Boltzmann two-term EEDF
746 if (m_distributionType == "Boltzmann-two-term") {
747 return m_eedfSolver->getElectronMobility();
748 } else {
749 throw NotImplementedError("PlasmaPhase::electronMobility",
750 "Electron mobility is only available for 'Boltzmann-two-term' "
751 "electron energy distributions.");
752 }
753}
754
755// ================================================================= //
756// Molar Thermodynamic Properties of the Solution //
757// ================================================================= //
758
760{
761 m_work.resize(m_kk);
763 double h = 0.0;
764 for (size_t k = 0; k < m_kk; ++k) {
765 h += moleFraction(k) * m_work[k];
766 }
767 return h;
768}
769
771{
772 m_work.resize(m_kk);
774 double u = 0.0;
775 for (size_t k = 0; k < m_kk; ++k) {
776 u += moleFraction(k) * m_work[k];
777 }
778 return u;
779}
780
782{
783 m_work.resize(m_kk);
785 double s = 0.0;
786 for (size_t k = 0; k < m_kk; ++k) {
787 s += moleFraction(k) * m_work[k];
788 }
789 return s;
790}
791
793{
794 m_work.resize(m_kk);
796 double g = 0.0;
797 for (size_t k = 0; k < m_kk; ++k) {
798 g += moleFraction(k) * m_work[k];
799 }
800 return g;
801}
802
803// ================================================================= //
804// Mechanical Equation of State //
805// ================================================================= //
806
808{
809 double T_g = temperature();
810 double T_e = electronTemperature();
812 return T_g + X_e * (T_e - T_g);
813}
814
815double PlasmaPhase::pressure() const {
817}
818
819
820// ================================================================= //
821// Chemical Potentials and Activities //
822// ================================================================= //
823
825{
826 return pressure() / (GasConstant * temperature());
827}
828
829void PlasmaPhase::getActivities(span<double> a) const
830{
831 double tmp = temperature() / meanTemperature();
832 for (size_t k = 0; k < nSpecies(); k++) {
833 a[k] = tmp * moleFraction(k);
834 }
835}
836
837void PlasmaPhase::getActivityCoefficients(span<double> ac) const
838{
839 checkArraySize("PlasmaPhase::getActivityCoefficients", ac.size(), m_kk);
840 double tmp = temperature() / meanTemperature();
841 for (size_t k = 0; k < m_kk; k++) {
842 ac[k] = tmp;
843 }
844}
845
846
847// ================================================================= //
848// Partial Molar Properties of the Solution //
849// ================================================================= //
850
851void PlasmaPhase::getChemPotentials(span<double> mu) const
852{
854 size_t k = m_electronSpeciesIndex;
855 double xx = std::max(SmallNumber, moleFraction(k));
856 mu[k] += (RTe() - RT()) * log(xx);
857}
858
859void PlasmaPhase::getPartialMolarEnthalpies(span<double> hbar) const
860{
861 // Since the `updateThermo` is overriden in `PlasmaPhase`,
862 // `enthalpy_RT_ref` returns \tilde{h}_k(T_k) / (R * T_k).
863 // When calling `IdealGasPhase::getPartialMolarEnthalpies(hbar)`,
864 // the `hbar` array is equal to \tilde{h}_k(T_k) * (R * T) / (R * T_k).
865 // For all heavy species, T_k == T, so we get \tilde{h}_k(T).
866 // For electrons, we need to multiply by T_e/T to get \tilde{h}_k(T_e).
869}
870
871void PlasmaPhase::getPartialMolarEntropies(span<double> sbar) const
872{
873 // Since the `updateThermo` is overriden in `PlasmaPhase`,
874 // `entropy_R_ref` returns s^\text{ref}_k(T_k)/R.
875 // When calling `IdealGasPhase::getPartialMolarEntropies(hbar)`,
876 // the `sbar` array is equal to s^\text{ref}_k(T_k)*R/R - R ln(X_k P/P^ref).
877 // Therefore, there is no need to correct for temperature.
879}
880
881void PlasmaPhase::getPartialMolarIntEnergies(span<double> ubar) const
882{
883 checkArraySize("PlasmaPhase::getPartialMolarIntEnergies", ubar.size(), m_kk);
884 auto _h = enthalpy_RT_ref();
885 for (size_t k = 0; k < m_kk; k++) {
886 ubar[k] = RT() * (_h[k] - 1.0);
887 }
888 // Redefine it for the electron species.
889 size_t k = m_electronSpeciesIndex;
890 ubar[k] = RTe() * (_h[k] - 1.0);
891}
892
893void PlasmaPhase::getPartialMolarVolumes(span<double> vbar) const
894{
895 double vol = RT() / pressure();
896 for (size_t k = 0; k < m_kk; k++) {
897 vbar[k] = vol;
898 }
899 vbar[m_electronSpeciesIndex] = RTe() / pressure();
900}
901
902// ================================================================= //
903// Properties of the Standard State of the Species in the Solution //
904// ================================================================= //
905
906void PlasmaPhase::getStandardChemPotentials(span<double> muStar) const
907{
908 // After calling PlasmaPhase::getGibbs_ref, muStar = mu^\text{ref}_k(T_k)(T_k).
909 // mu^\text{ref} is evaluated at T for heavy species and at Te for electrons.
910 getGibbs_ref(muStar);
911
912 // Then, we need to add R*T_k*ln(P/Pref) to mu^\text{ref}.
913 // .. For heavy species, mu_star = mu^\text{ref}(T) + R*T*ln(P/Pref)
914 double tmp = log(pressure() / refPressure()) * RT();
915 for (size_t k = 0; k < m_kk; k++) {
916 muStar[k] += tmp;
917 }
918 // .. For electrons, mu_star = mu^\text{ref}(Te) + R*T_e*ln(P/Pref)
919 size_t k = m_electronSpeciesIndex;
920 muStar[k] -= log(pressure() / refPressure()) * RT();
921 muStar[k] += log(pressure() / refPressure()) * RTe();
922}
923
924void PlasmaPhase::getStandardVolumes(span<double> vol) const
925{
926 double tmp = RT() / pressure();
927 for (size_t k = 0; k < m_kk; k++) {
928 vol[k] = tmp;
929 }
931}
932
933// ================================================================= //
934// Thermodynamic Values for the Species Reference States //
935// ================================================================= //
936
937void PlasmaPhase::getGibbs_ref(span<double> g) const
938{
939 // Since the `updateThermo` is overriden in `PlasmaPhase`,
940 // `gibbs_RT_ref` returns \mu^\text{ref}_k(T_k) / (R * T_k).
941 // When calling `IdealGasPhase::getGibbs_ref(g)`,
942 // the `g` array is equal to \mu^\text{ref}_k(T_k) * (R * T) / (R * T_k).
943 // For all heavy species, T_k == T, so we get \mu^\text{ref}_k(T).
944 // For electrons, we need to multiply by T_e/T to get \mu^\text{ref}_k(T_e).
947}
948
949void PlasmaPhase::getStandardVolumes_ref(span<double> vol) const
950{
953}
954
955// ================================================================= //
956// Setting the State //
957// ================================================================= //
958
959void PlasmaPhase::setState(const AnyMap& input_state)
960{
961 AnyMap state = input_state;
962
963 // Set electron temperature first.
964 if (state.hasKey("electron-temperature")) {
965 state["Te"] = state["electron-temperature"];
966 }
967
968 if (state.hasKey("Te")) {
969 setElectronTemperature(state.convert("Te", "K"));
970 }
971
972 // Remap allowable synonyms for gas temperature after setting electron temperature,
973 if (state.hasKey("gas-temperature")) {
974 state["T"] = state["gas-temperature"];
975 }
976 if (state.hasKey("Tg")) {
977 state["T"] = state["Tg"];
978 }
979
980 // Call the base class method to set the remaining state variables.
982}
983
985{
986 // sigma = e * n_e * mu_e [S/m]; q_J = sigma * E^2 [W/m^3]
987 const double mu_e = electronMobility(); // m^2 / (V·s)
988 if (mu_e <= 0.0) {
989 return 0.0;
990 }
991 const double ne = concentration(m_electronSpeciesIndex) * Avogadro; // m^-3
992 if (ne <= 0.0) {
993 return 0.0;
994 }
995 const double E = electricField(); // V/m
996 if (E <= 0.0) {
997 return 0.0;
998 }
999 const double sigma = ElectronCharge * ne * mu_e; // S/m
1000 return sigma * E * E; // W/m^3
1001}
1002
1004{
1005 // Joule heating: sigma * E^2 [W/m^3]
1006 const double qJ = jouleHeatingPower();
1007 checkFinite(qJ);
1008
1009 return qJ;
1010}
1011}
EEDF Two-Term approximation solver.
Header for plasma reaction rates parameterized by electron collision cross section and electron energ...
Base class for kinetics managers and also contains the kineticsmgr module documentation (see Kinetics...
Header file for class PlasmaPhase.
Declaration for class Cantera::Species.
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
long int getInt(const string &key, long int default_) const
If key exists, return it as a long int, otherwise return default_.
Definition AnyMap.cpp:1585
double getDouble(const string &key, double default_) const
If key exists, return it as a double, otherwise return default_.
Definition AnyMap.cpp:1580
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1477
double convert(const string &key, const string &units) const
Convert the item stored by the given key to the units specified in units.
Definition AnyMap.cpp:1595
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition AnyMap.cpp:1575
const string & getString(const string &key, const string &default_) const
If key exists, return it as a string, otherwise return default_.
Definition AnyMap.cpp:1590
Base class for exceptions thrown by Cantera classes.
Electron collision plasma reaction rate type.
void updateInterpolatedCrossSection(span< const double >)
Update the value of m_crossSectionsInterpolated [m2].
void getGibbs_ref(span< double > g) const override
Returns the vector of the Gibbs function of the reference state at the current temperature of the sol...
void getPartialMolarEnthalpies(span< double > hbar) const override
Returns an array of partial molar enthalpies for the species in the mixture.
vector< double > m_g0_RT
Temporary storage for dimensionless reference state Gibbs energies.
vector< double > m_h0_RT
Temporary storage for dimensionless reference state enthalpies.
span< const double > enthalpy_RT_ref() const
Returns a reference to the dimensionless reference state enthalpy vector.
virtual void updateThermo() const
Update the species reference state thermodynamic functions.
vector< double > m_s0_R
Temporary storage for dimensionless reference state entropies.
void getPartialMolarEntropies(span< double > sbar) const override
Returns an array of partial molar entropies of the species in the solution.
vector< double > m_cp0_R
Temporary storage for dimensionless reference state heat capacities.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
void getChemPotentials(span< double > mu) const override
Get the species chemical potentials. Units: J/kmol.
void getStandardVolumes_ref(span< double > vol) const override
Get the molar volumes of the species reference states at the current T and P_ref of the solution.
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition AnyMap.h:749
virtual void update_single(size_t k, double T, double &cp_R, double &h_RT, double &s_R) const
Get reference-state properties for a single species.
An error indicating that an unimplemented function has been called.
ValueCache m_cache
Cached for saved calculations within each ThermoPhase.
Definition Phase.h:862
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:247
size_t m_kk
Number of species in the phase.
Definition Phase.h:882
size_t speciesIndex(const string &name, bool raise=true) const
Returns the index of a species named 'name' within the Phase object.
Definition Phase.cpp:127
double temperature() const
Temperature (K).
Definition Phase.h:586
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:677
virtual double concentration(const size_t k) const
Concentration of species k.
Definition Phase.cpp:495
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition Phase.cpp:457
virtual double density() const
Density (kg/m^3).
Definition Phase.h:611
double molecularWeight(size_t k) const
Molecular weight of species k.
Definition Phase.cpp:398
string name() const
Return the name of the phase.
Definition Phase.cpp:20
void checkElectronEnergyDistribution() const
Check the electron energy distribution.
void getStandardChemPotentials(span< double > muStar) const override
Return the standard chemical potentials of the species. Units: J/kmol.
vector< vector< double > > m_energyLevels
Electron energy levels corresponding to the cross section data.
void setCollisions()
Set collisions.
double meanElectronEnergy() const
Mean electron energy [eV].
void getGibbs_ref(span< double > g) const override
Return the reference chemical potentials of the species. Units: J/kmol.
double m_electronTempEquil
Saved electron temperature during an equilibrium solve.
double enthalpy_mole() const override
Return the Molar enthalpy. Units: J/kmol.
size_t m_nPoints
Number of points of electron energy levels.
void setState(const AnyMap &state) override
Set the state using an AnyMap containing any combination of properties supported by the thermodynamic...
void getActivities(span< double > a) const override
Get the array of non-dimensional activities at the current solution temperature, pressure,...
void addCollision(shared_ptr< Reaction > collision)
Add a collision and record the target species.
virtual void setSolution(std::weak_ptr< Solution > soln) override
Set the link to the Solution object that owns this ThermoPhase.
void normalizeElectronEnergyDistribution()
Electron energy distribution norm.
void updateThermo() const override
Update the species reference state thermodynamic functions.
void getPartialMolarEnthalpies(span< double > hbar) const override
Return the partial molar enthalpies of the species in the solution. Units: J/kmol.
vector< size_t > m_targetSpeciesIndices
The collision-target species indices of m_collisions.
void setElectronTemperature(double Te) override
Set the internally stored electron temperature of the phase [K].
void electronEnergyLevelChanged()
When electron energy level changed, plasma properties such as electron-collision reaction rates need ...
double pressure() const override
Return the pressure of the plasma phase. Units: Pa.
double elasticPowerLoss()
The elastic power loss [J/s/m³].
int m_levelNum
Electron energy level change variable.
bool updateInterpolatedCrossSection(size_t k)
Update interpolated cross section of a collision.
bool m_inEquilibrate
Lock flag (default off)
void electronEnergyDistributionChanged()
When electron energy distribution changed, plasma properties such as electron-collision reaction rate...
size_t nElectronEnergyLevels() const
Number of electron levels.
size_t nCollisions() const
Number of electron collision cross sections.
void endEquilibrate() override
Hook called at the end of an equilibrium calculation on this phase.
Eigen::ArrayXd m_electronEnergyDist
Normalized electron energy distribution vector [-] Length: m_nPoints.
double electricField() const
Get the applied electric field strength [V/m].
Eigen::ArrayXd m_electronEnergyLevels
electron energy levels [ev]. Length: m_nPoints
void getActivityCoefficients(span< double > ac) const override
Get the array of non-dimensional activity coefficients at the current solution temperature,...
double meanTemperature() const
Return the mean temperature of the plasma phase. Units: K.
double intrinsicHeating() override
Intrinsic volumetric heating rate [W/m³].
double electronMobility() const
The electron mobility (m²/V/s)
void getParameters(AnyMap &phaseNode) const override
Store the parameters of a ThermoPhase object such that an identical one could be reconstructed using ...
string type() const override
String indicating the thermodynamic model implemented.
void checkElectronEnergyLevels() const
Check the electron energy levels.
void initThermo() override
Initialize the ThermoPhase object after all species have been set up.
void updateElasticElectronEnergyLossCoefficients()
Update elastic electron energy loss coefficients.
void updateElectronTemperatureFromEnergyDist()
Update electron temperature (K) From energy distribution.
string m_distributionType
Electron energy distribution type. Can be "isotropic", "discretized" or "Boltzmann-two-term".
void updateElectronEnergyDistribution()
Update the electron energy distribution.
vector< double > m_elasticElectronEnergyLossCoefficients
Elastic electron energy loss coefficients (eV m3/s)
string m_quadratureMethod
Numerical quadrature method for electron energy distribution.
PlasmaPhase(const string &inputFile="", const string &id="")
Construct and initialize a PlasmaPhase object directly from an input file.
void beginEquilibrate() override
Hook called at the beginning of an equilibrium calculation on this phase.
void setDiscretizedElectronEnergyDist(span< const double > levels, span< const double > distrb)
Set discretized electron energy distribution.
double m_electronTemp
Electron temperature [K].
double RTe() const
Return the Gas Constant multiplied by the current electron temperature [J/kmol].
double intEnergy_mole() const override
Return the molar internal energy. Units: J/kmol.
double entropy_mole() const override
Return the molar entropy. Units: J/kmol/K.
bool m_do_normalizeElectronEnergyDist
Flag of normalizing electron energy distribution.
void updateElectronEnergyDistDifference()
Update electron energy distribution difference.
void updateElasticElectronEnergyLossCoefficient(size_t i)
Updates the elastic electron energy loss coefficient for collision index i.
vector< size_t > m_kElastic
Indices of elastic collisions in m_crossSections.
unique_ptr< EEDFTwoTermApproximation > m_eedfSolver
Solver used to calculate the EEDF based on electron collision rates.
string electronSpeciesName() const
Electron species name.
void setElectronEnergyDistributionParameters(const AnyMap &eedf)
Set parameters for the electron energy distribution.
void setIsotropicElectronEnergyDistribution()
Set isotropic electron energy distribution.
void getPartialMolarVolumes(span< double > vbar) const override
Return the partial molar volumes of the species in the solution. Units: m³/kmol.
Eigen::ArrayXd m_electronEnergyDistDiff
ionization degree for the electron-electron collisions (tmp is the previous one)
void getStandardVolumes(span< double > vol) const override
Return the standard molar volumes of the species. Units: m³/kmol.
void getPartialMolarEntropies(span< double > sbar) const override
Return the partial molar entropies of the species in the solution. Units: J/kmol/K.
double gibbs_mole() const override
Return the molar Gibbs free energy. Units: J/kmol.
double standardConcentration(size_t k=0) const override
Returns the standard concentration , which is used to normalize the generalized concentration.
std::vector< double > m_work
Work array.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
const shared_ptr< Reaction > collision(size_t i) const
Get the Reaction object associated with electron collision i.
vector< bool > m_interp_cs_ready
The list of whether the interpolated cross sections is ready.
vector< shared_ptr< ElectronCollisionPlasmaRate > > m_collisionRates
The list of shared pointers of collision rates.
void getChemPotentials(span< double > mu) const override
Return the chemical potentials of the species in the solution. Units: J/kmol.
void setElectronEnergyLevels(span< const double > levels)
Set electron energy levels.
vector< shared_ptr< Reaction > > m_collisions
The list of shared pointers of plasma collision reactions.
void setParameters(const AnyMap &phaseNode, const AnyMap &rootNode=AnyMap()) override
Set equation of state parameters from an AnyMap phase description.
void setMeanElectronEnergy(double energy)
Set mean electron energy [eV].
void getStandardVolumes_ref(span< double > vol) const override
Return the molar volumes of the species reference states. Units: m³/kmol.
size_t m_electronSpeciesIndex
Index of electron species.
vector< vector< double > > m_crossSections
Cross section data.
void setElectronEnergyDistributionType(const string &type)
Set electron energy distribution type.
double jouleHeatingPower() const
The joule heating power (W/m³)
vector< size_t > m_kInelastic
Indices of inelastic collisions in m_crossSections.
double electronTemperature() const override
Electron Temperature [K].
void setIsotropicShapeFactor(double x)
Set the shape factor of isotropic electron energy distribution.
void enableNormalizeElectronEnergyDist(bool enable)
Set flag of automatically normalize electron energy distribution.
void getPartialMolarIntEnergies(span< double > ubar) const override
Return the partial molar internal energies of the species in the solution. Units: J/kmol.
int m_distNum
Electron energy distribution change variable.
virtual void endEquilibrate()
Hook called at the end of an equilibrium calculation on this phase.
virtual void setParameters(const AnyMap &phaseNode, const AnyMap &rootNode=AnyMap())
Set equation of state parameters from an AnyMap phase description.
virtual void getParameters(AnyMap &phaseNode) const
Store the parameters of a ThermoPhase object such that an identical one could be reconstructed using ...
virtual void setState(const AnyMap &state)
Set the state using an AnyMap containing any combination of properties supported by the thermodynamic...
double RT() const
Return the Gas Constant multiplied by the current temperature.
virtual void setSolution(std::weak_ptr< Solution > soln)
Set the link to the Solution object that owns this ThermoPhase.
virtual void initThermo()
Initialize the ThermoPhase object after all species have been set up.
void initThermoFile(const string &inputFile, const string &id)
Initialize a ThermoPhase object using an input file.
std::weak_ptr< Solution > m_soln
reference to Solution
virtual void beginEquilibrate()
Hook called at the beginning of an equilibrium calculation on this phase.
MultiSpeciesThermo m_spthermo
Pointer to the calculation manager for species reference-state thermodynamic properties.
virtual double refPressure() const
Returns the reference pressure in Pa.
CachedScalar getScalar(int id)
Get a reference to a CachedValue object representing a scalar (double) with the given id.
Definition ValueCache.h:161
int getId()
Get a unique id for a cached value.
Header for a file containing miscellaneous numerical functions.
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
double numericalQuadrature(const string &method, const Eigen::ArrayXd &f, const Eigen::ArrayXd &x)
Numerical integration of a function.
Definition funcs.cpp:116
const double Boltzmann
Boltzmann constant [J/K].
Definition ct_defs.h:87
const double Avogadro
Avogadro's Number [number/kmol].
Definition ct_defs.h:84
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
const double ElectronCharge
Elementary charge [C].
Definition ct_defs.h:93
const double ElectronMass
Electron Mass [kg].
Definition ct_defs.h:114
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:263
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:183
void checkFinite(const double tmp)
Check to see that a number is finite (not NaN, +Inf or -Inf)
MappedVector asVectorXd(vector< double > &v)
Convenience wrapper for accessing std::vector as an Eigen VectorXd.
Definition eigen_dense.h:60
span< double > asSpan(Eigen::DenseBase< Derived > &v)
Convenience wrapper for accessing Eigen vector/array/map data as a span.
Definition eigen_dense.h:46
const double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:161
map< string, double > Composition
Map from string names to doubles.
Definition ct_defs.h:180
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
A cached property value and the state at which it was evaluated.
Definition ValueCache.h:33
double state2
Value of the second state variable for the state at which value was evaluated, for example density or...
Definition ValueCache.h:106
bool validate(double state1New)
Check whether the currently cached value is valid based on a single state variable.
Definition ValueCache.h:39
double state1
Value of the first state variable for the state at which value was evaluated, for example temperature...
Definition ValueCache.h:102