rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
Loading...
Searching...
No Matches
PlasmaReactor0D.cpp
Go to the documentation of this file.
1#include "PlasmaReactor0D.h"
2#include "numeric/Format.h"
5#include "units.h"
6
9
10#include <algorithm>
11#include <cmath>
12#include <sstream>
13#include <stdexcept>
14
15namespace rizer
16{
17
18 // ------------------------------------------------------------------------------ //
19 // Constructor //
20 // ------------------------------------------------------------------------------ //
21
23 : m_sol(loadMechanism(cfg.mech, cfg.phase)), m_gap(cfg.gap)
24 {
25 auto thermo = m_sol->thermo();
26 const std::size_t nsp = thermo->nSpecies();
27 m_plasma = dynamic_cast<Cantera::PlasmaPhase *>(thermo.get());
28 if (m_plasma == nullptr)
29 {
30 throw std::invalid_argument(
31 "PlasmaReactor0D: phase \"" + cfg.phase + "\" in \"" + cfg.mech +
32 "\" is a " + thermo->type() +
33 ", not a PlasmaPhase, so it carries no electron temperature.");
34 }
35 m_ie = m_plasma->electronSpeciesIndex();
36 // Fill molar_mass [kg/mol] and is_electron into the per-species collision specs.
37 std::vector<double> wt(thermo->molecularWeights().begin(),
38 thermo->molecularWeights().end()); // kg/kmol
39 // Refused rather than resized: padding gives the missing species no
40 // collision model and truncating drops the caller's tail, so either
41 // way a species silently leaves every collision sum.
42 if (cfg.specs.size() != nsp)
43 {
44 throw std::invalid_argument(
45 "PlasmaReactor0D: got collision specs for " +
46 std::to_string(cfg.specs.size()) + " species, but \"" + cfg.mech +
47 "\" has " + std::to_string(nsp) +
48 " species. Give one spec per mechanism species, in its order.");
49 }
50 std::vector<CollisionModel::SpeciesSpec> specs = cfg.specs;
51 for (std::size_t k = 0; k < nsp; k++)
52 {
53 specs[k].molar_mass = wt[k] * 1.0e-3; // kg/kmol -> kg/mol
54 specs[k].is_electron = (k == m_ie);
55 }
56 // CollisionModel tabulates composition-resolved transport/rate coefficients
57 // (e.g. mobility, collision frequency) over the electron-temperature grid
58 // [Te_min, Te_max] with Te_n points.
59 CollisionModel collision(specs, cfg.Te_min, cfg.Te_max, cfg.Te_n);
60
61 // PlasmaCell is the physics core shared with PlasmaChannel1D: chemistry source
62 // terms (if reacting), Joule heating, and elastic/inelastic Tg<->Te exchange.
64 rc.reacting = cfg.reacting;
65 rc.collision = std::move(collision);
66 rc.species_h0k = cfg.species_h0k;
67 m_rhs = std::make_unique<PlasmaCell>(m_sol, std::move(rc));
68 }
69
70 // ------------------------------------------------------------------------------ //
71 // Public methods //
72 // ------------------------------------------------------------------------------ //
73
74 std::vector<std::string> PlasmaReactor0D::speciesNames() const
75 {
76 std::vector<std::string> names(m_rhs->nSpecies());
77 for (std::size_t k = 0; k < names.size(); k++)
78 names[k] = m_rhs->speciesName(k);
79 return names;
80 }
81
82 void PlasmaReactor0D::rhs(double Tg, double Te, double V, const double *Y, double E,
83 double mass, double p_ext, double polytropic_index,
84 double *dydt) const
85 {
86 // Three steps:
87 // 1. the shared cell's source terms, at the density the volume implies
88 // 2. the polytropic volume equation, and the pressure work it does on
89 // each pool -- skipped entirely at constant volume
90 // 3. pack dydt = [dTg, dTe, dV, dY_0..dY_{K-1}]
91
92 // ---- 1. the shared cell at this state -------------------------------
93 // Constant-mass reactor: density is derived from the instantaneous volume
94 // rather than integrated directly (mirrors Isomass2TVolumeReactor in Python).
95 const double rho = mass / V;
96 // dTg, dTe accumulate the RHS's energy-balance source terms [K/s]; lhsTg,
97 // lhsTe are the (heat-capacity-like) coefficients multiplying dTg/dt, dTe/dt
98 // on the LHS of the energy balance, so source terms below can be added as
99 // @f$ \text{source} / \text{lhs} @f$.
100 // +inf is the constant-volume case. Any other index has to be a usable
101 // polytropic one: running it at constant volume would drop the
102 // requested expansion without a word. Checked before the source
103 // terms, so a setup error is not hidden behind a recoverable
104 // TrialStateError from a refused state.
105 const bool constant_volume = std::isinf(polytropic_index) && polytropic_index > 0.0;
106 if (!constant_volume && !(polytropic_index > 0.0))
107 {
108 throw std::invalid_argument(
109 "PlasmaReactor0D::rhs: polytropic_index must be positive, or +inf for a "
110 "constant volume; got " + significant(polytropic_index) + ".");
111 }
112 if (!constant_volume && !(m_gap > 0.0))
113 {
114 throw std::invalid_argument(
115 "PlasmaReactor0D::rhs: a finite polytropic_index needs gap > 0, since "
116 "the rarefaction timescale comes from that length; got gap = " +
117 significant(m_gap) + " m.");
118 }
119 double dTg = 0.0, dTe = 0.0, lhsTg = 1.0, lhsTe = 1.0;
120 // dY goes straight into dydt[3..]; rates() leaves thermo at (Tg,Te,Y,rho).
121 // &m_diag caches the power/Maxwellian-condition breakdown for diagnostics().
122 m_rhs->sourceTerms(Tg, Te, Y, rho, E, dTg, dTe, dydt + 3, lhsTg, lhsTe, &m_diag);
123
124 // ---- 2. polytropic volume, and its pressure work --------------------
125 double dV = 0.0;
126 if (!constant_volume)
127 {
128 // Polytropic rarefaction (mirrors Isomass2TVolumeReactor): the hot
129 // core's overpressure relaxes toward p_ext over the acoustic (rarefaction)
130 // timescale tau derived below.
131 auto thermo = m_rhs->solution()->thermo();
132 const TwoTemperatureState eos = readTwoTemperatureState(*m_plasma);
133 const double P = eos.pressure;
134 // Electron partial pressure [Pa], PHYSICS.md (G6): @f$p_e = n_e k_B T_e@f$,
135 // taken from PlasmaPhase rather than as the Dalton share @f$X_e P@f$. The
136 // two differ by @f$\overline{T}/T_e@f$, because Cantera's P is
137 // @f$n_\text{tot}k_B\overline{T}@f$ -- a factor ~10 at
138 // @f$T_e = 10 T_g@f$, all of it in the pressure work below.
139 const double p_e = eos.electron_pressure;
140 // The temperatures need no check of their own here: step 1's
141 // sourceTerms() already refused, as a recoverable TrialStateError,
142 // any Tg outside [TemperatureFloor, TemperatureCeiling] and any Te
143 // under the floor. Te above the ceiling is valid: the electron's
144 // cp/R is the constant 2.5, so the mixture cp and cv below hold.
145 // Two-temperature mixture temperature, the effective temperature of
146 // the speed-of-sound estimate below. Read from the phase rather than
147 // rebuilt: PlasmaPhase::meanTemperature() is
148 // @f$\overline{T} = (1 - X_e) T_g + X_e T_e@f$ by definition, and it
149 // is the same mean the `pressure` above was computed with.
150 const double Tmean = eos.mean_temperature;
151 const double cp = thermo->cp_mass();
152 const double cv = thermo->cv_mass();
153 // A gas mixture has c_v > 0; anything else means the state handed to
154 // the thermo layer is not one, and the speed of sound below has no
155 // meaning. Raised rather than substituted: a stand-in gamma would
156 // return a plausible rarefaction rate computed from nothing.
157 if (!(cv > 0.0))
158 {
159 std::ostringstream message;
160 message << "PlasmaReactor0D::rhs: heat capacity at constant "
161 "volume is "
162 << std::scientific << cv
163 << " J/kg/K at Tg=" << Tg << " K, Te=" << Te
164 << " K, rho=" << rho
165 << " kg/m^3. It must be positive for the polytropic "
166 "volume equation's speed of sound. Check the "
167 "composition and the temperatures at this state.";
168 throw std::runtime_error(message.str());
169 }
170 const double gamma = cp / cv;
171 const double M = thermo->meanMolecularWeight() * 1.0e-3; // kg/mol
172 // Treat the plasma volume as a cylinder of fixed axial length m_gap to
173 // recover an effective radius r, then the ideal-gas speed of sound at
174 // @f$ \overline{T} @f$ and the acoustic (rarefaction) timescale tau over
175 // which the core relaxes toward p_ext:
176 // @f[
177 // r = \sqrt{\frac{V}{\text{m\_gap} \, \pi}}, \qquad
178 // c_\text{sound} = \sqrt{\frac{\gamma R \overline{T}}{M}}, \qquad
179 // \tau = \frac{r}{c_\text{sound}}.
180 // @f]
181 const double radius = std::sqrt(V / (m_gap * units::pi));
182 const double c_sound = std::sqrt(gamma * units::R * Tmean / M);
183 const double tau = radius / c_sound;
184 // Polytropic volume equation (mirrors Isomass2TVolumeReactor): the
185 // pressure relaxes toward p_ext over tau, and the volume responds via the
186 // polytropic relation with index n:
187 // @f[
188 // \frac{dp}{dt} = -\frac{P - p_\text{ext}}{\tau}, \qquad
189 // \frac{dV}{dt} = -\frac{1}{n} \frac{V}{P} \frac{dp}{dt}.
190 // @f]
191 const double dp_dt = -(P - p_ext) / tau;
192 dV = -1.0 / polytropic_index * (V / P) * dp_dt;
193 // Raised, never zeroed, for the reason PlasmaCell::sourceTerms gives:
194 // a substituted 0 hands the integrator a smooth derivative it accepts,
195 // and the volume would freeze on a state the solver never asked about.
196 if (!std::isfinite(dV))
197 {
198 std::ostringstream message;
199 message << "PlasmaReactor0D::rhs: non-finite volume rate dV/dt = "
200 << dV << " m^3/s at P = " << P << " Pa, p_ext = " << p_ext
201 << " Pa, V = " << V << " m^3, tau = " << tau
202 << " s, polytropic index " << polytropic_index << ".";
203 throw std::runtime_error(message.str());
204 }
205 // Pressure work removes energy from each population as the cell expands.
206 dTe -= (p_e * dV / V) / lhsTe;
207 dTg -= ((P - p_e) * dV / V) / lhsTg;
208 }
209
210 // ---- 3. pack the state derivative -----------------------------------
211 dydt[0] = dTg;
212 dydt[1] = dTe;
213 dydt[2] = dV;
214 // dydt[3..] (dY) already written by rates().
215 }
216
217} // namespace rizer
Number formatting for exception messages.
The standalone 0-D two-temperature reactor: PlasmaCell plus the constant-mass volume equation,...
The two-temperature quantities every model in this extension reads back from a PlasmaPhase,...
size_t electronSpeciesIndex() const
Composition-resolved electron-heavy momentum-transfer collision model.
void rhs(double Tg, double Te, double V, const double *Y, double E, double mass, double p_ext, double polytropic_index, double *dydt) const
Full 0D RHS.
PlasmaReactor0D(const PlasmaReactor0DConfig &cfg)
Construct from a mechanism/phase and collision-model configuration.
std::vector< std::string > speciesNames() const
Species names in mechanism index order, so a caller passing plain numbers across the Python boundary ...
constexpr double R
Ideal gas constant [J/(mol K)].
Definition units.h:58
constexpr double pi
pi (matches numpy's np.pi used in units.py).
Definition units.h:51
std::shared_ptr< Cantera::Solution > loadMechanism(const std::string &mech, const std::string &phase)
Load a mechanism with rizer's custom rate types registered first.
std::string significant(double value)
value to 6 significant digits, for exception messages.
Definition Format.h:18
TwoTemperatureState readTwoTemperatureState(const Cantera::PlasmaPhase &phase)
Read the two-temperature EOS back from a phase whose state is set.
Definition PlasmaState.h:84
CollisionModel collision
Composition-resolved sigma/nu_E (preferred).
Definition PlasmaCell.h:54
bool reacting
Include finite-rate chemistry source terms (true) or freeze composition (false) [-].
Definition PlasmaCell.h:52
std::vector< double > species_h0k
Per-species standard enthalpy of formation at 0 K [J/kmol], length nSpecies(), in mechanism species-i...
Definition PlasmaCell.h:83
double Te_max
Upper bound of the tabulated electron-temperature grid [K].
double Te_min
Lower bound of the tabulated electron-temperature grid [K].
std::string phase
Mechanism file/URI, and phase name within it.
std::vector< CollisionModel::SpeciesSpec > specs
Per-species cross-section model (cross-section data / radius / ion Z), exactly one entry per mechanis...
std::size_t Te_n
Number of points in the tabulated Te grid [-].
std::vector< double > species_h0k
Per-species standard enthalpy of formation at 0 K [J/kmol]; forwarded to PlasmaCellConfig::species_h0...
bool reacting
If false, disables chemistry source terms in the RHS.
The two-temperature equation of state at the state currently set on a phase, PHYSICS....
Definition PlasmaState.h:72
double mean_temperature
[K], the one pressure uses
Definition PlasmaState.h:77
double pressure
Total [Pa].
Definition PlasmaState.h:73
Physical constants and unit conversions, in SI by default.