rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
Loading...
Searching...
No Matches
Plasma0DReactor.cpp
Go to the documentation of this file.
1#include "Plasma0DReactor.h"
3#include "units.h"
4
6
7#include <algorithm>
8#include <cmath>
9
10namespace rizer {
11
12namespace {
13Cantera::shared_ptr<Cantera::Solution>
14loadMechanism(const std::string& mech, const std::string& phase)
15{
16 registerPlasmaRates(); // Idempotent
17 return Cantera::newSolution(mech, phase);
18}
19} // namespace
20
22 : m_sol(loadMechanism(cfg.mech, cfg.phase))
23 , m_gap(cfg.gap)
24{
25 auto thermo = m_sol->thermo();
26 const std::size_t nsp = thermo->nSpecies();
27 // Locate the electron pseudo-species; its index is used throughout (mole/mass
28 // fraction lookups, is_electron flag below, and the partial-pressure split in
29 // rhs()).
30 m_ie = thermo->speciesIndex("e-");
31 if (m_ie == Cantera::npos) {
32 throw std::invalid_argument("Plasma0DReactor: mechanism has no 'e-' species.");
33 }
34 // Fill molar_mass [kg/mol] and is_electron into the per-species collision specs.
35 std::vector<double> wt(thermo->molecularWeights().begin(),
36 thermo->molecularWeights().end()); // kg/kmol
37 std::vector<CollisionModel::SpeciesSpec> specs = cfg.specs;
38 specs.resize(nsp);
39 for (std::size_t k = 0; k < nsp; k++) {
40 specs[k].molar_mass = wt[k] * 1.0e-3; // kg/kmol -> kg/mol
41 specs[k].is_electron = (k == m_ie);
42 }
43 // CollisionModel tabulates composition-resolved transport/rate coefficients
44 // (e.g. mobility, collision frequency) over the electron-temperature grid
45 // [Te_min, Te_max] with Te_n points; spitzer toggles the Coulomb contribution.
46 CollisionModel collision(specs, cfg.Te_min, cfg.Te_max, cfg.Te_n, cfg.spitzer);
47
48 // ReactorRHS is the physics core shared with PlasmaChannel1D: chemistry source
49 // terms (if reacting), Joule heating, and elastic/inelastic Tg<->Te exchange.
51 rc.reacting = cfg.reacting;
52 rc.collision = std::move(collision);
53 rc.species_h0k = cfg.species_h0k;
54 m_rhs = std::make_unique<ReactorRHS>(m_sol, std::move(rc));
55}
56
57std::vector<std::string> Plasma0DReactor::speciesNames() const
58{
59 std::vector<std::string> names(m_rhs->nSpecies());
60 for (std::size_t k = 0; k < names.size(); k++) names[k] = m_rhs->speciesName(k);
61 return names;
62}
63
64void Plasma0DReactor::rhs(double Tg, double Te, double V, const double* Y, double E,
65 double mass, double p_ext, double polytropic_index,
66 double* dydt) const
67{
68 // Constant-mass reactor: density is derived from the instantaneous volume rather
69 // than integrated directly (mirrors Isomass2TVolumeReactor in Python).
70 const double rho = mass / V;
71 // dTg, dTe accumulate the RHS's energy-balance source terms [K/s]; lhsTg,
72 // lhsTe are the (heat-capacity-like) coefficients multiplying dTg/dt, dTe/dt
73 // on the LHS of the energy balance, so source terms below can be added as
74 // @f$ \text{source} / \text{lhs} @f$.
75 double dTg = 0.0, dTe = 0.0, lhsTg = 1.0, lhsTe = 1.0;
76 // dY goes straight into dydt[3..]; rates() leaves thermo at (Tg,Te,Y,rho).
77 // &m_diag caches the power/Maxwellian-condition breakdown for diagnostics().
78 m_rhs->rates(Tg, Te, Y, rho, E, dTg, dTe, dydt + 3, lhsTg, lhsTe, &m_diag);
79
80 double dV = 0.0;
81 if (std::isfinite(polytropic_index) && polytropic_index > 0.0 && m_gap > 0.0) {
82 // Polytropic rarefaction (mirrors Isomass2TVolumeReactor): the hot
83 // core's overpressure relaxes toward p_ext over the acoustic (rarefaction)
84 // timescale tau derived below.
85 auto thermo = m_rhs->solution()->thermo();
86 const double P = thermo->pressure();
87 const double Xe = thermo->moleFraction(m_ie);
88 // Electron partial pressure [Pa], via the ideal-gas Dalton split
89 // @f$ p_e = X_e P @f$.
90 const double p_e = Xe * P;
91 // Clamp to avoid unphysical/singular values (e.g. at startup) feeding
92 // sqrt() below.
93 const double Tg_c = std::max(Tg, 200.0);
94 const double Te_c = std::max(Te, 200.0);
95 // Two-temperature mixture temperature (mole-fraction-weighted blend of Tg
96 // and Te), used as the effective temperature for the speed-of-sound
97 // estimate below:
98 // @f[
99 // \overline{T} = (1 - X_e) T_g + X_e T_e.
100 // @f]
101 const double Tmean = (1.0 - Xe) * Tg_c + Xe * Te_c;
102 const double cp = thermo->cp_mass();
103 const double cv = thermo->cv_mass();
104 // Fall back to diatomic-air @f$ \gamma @f$ if @f$ c_v \approx 0 @f$.
105 const double gamma = (cv > 0.0) ? cp / cv : 1.4;
106 const double M = thermo->meanMolecularWeight() * 1.0e-3; // kg/mol
107 // Treat the plasma volume as a cylinder of fixed axial length m_gap to
108 // recover an effective radius r, then the ideal-gas speed of sound at
109 // @f$ \overline{T} @f$ and the acoustic (rarefaction) timescale tau over
110 // which the core relaxes toward p_ext:
111 // @f[
112 // r = \sqrt{\frac{V}{\text{m\_gap} \, \pi}}, \qquad
113 // c_\text{sound} = \sqrt{\frac{\gamma R \overline{T}}{M}}, \qquad
114 // \tau = \frac{r}{c_\text{sound}}.
115 // @f]
116 const double radius = std::sqrt(V / (m_gap * units::pi));
117 const double c_sound = std::sqrt(gamma * units::R * Tmean / M);
118 const double tau = radius / c_sound;
119 // Polytropic volume equation (mirrors Isomass2TVolumeReactor): the
120 // pressure relaxes toward p_ext over tau, and the volume responds via the
121 // polytropic relation with index n:
122 // @f[
123 // \frac{dp}{dt} = -\frac{P - p_\text{ext}}{\tau}, \qquad
124 // \frac{dV}{dt} = -\frac{1}{n} \frac{V}{P} \frac{dp}{dt}.
125 // @f]
126 const double dp_dt = -(P - p_ext) / tau;
127 dV = -1.0 / polytropic_index * (V / P) * dp_dt;
128 // Pressure work removes energy from each population as the cell expands.
129 if (std::isfinite(dV)) {
130 dTe -= (p_e * dV / V) / lhsTe;
131 dTg -= ((P - p_e) * dV / V) / lhsTg;
132 } else {
133 dV = 0.0;
134 }
135 }
136
137 dydt[0] = dTg;
138 dydt[1] = dTe;
139 dydt[2] = dV;
140 // dydt[3..] (dY) already written by rates().
141}
142
143} // namespace rizer
Composition-resolved electron-heavy momentum-transfer collision model.
std::vector< std::string > speciesNames() const
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.
Plasma0DReactor(const Config &cfg)
Construct from a mechanism/phase and collision-model configuration.
shared_ptr< Solution > newSolution(const string &infile, const string &name="", const string &transport="default", const vector< shared_ptr< Solution > > &adjacent={})
const size_t npos
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
void registerPlasmaRates()
Register the custom plasma reaction rates with Cantera's ReactionRateFactory.
bool spitzer
Use Spitzer (Coulomb) conductivity contribution when true.
std::size_t Te_n
Number of points in the tabulated Te grid [-].
double Te_max
Upper bound of the tabulated electron-temperature grid [K].
bool reacting
If false, disables chemistry source terms in the RHS.
std::vector< CollisionModel::SpeciesSpec > specs
Per-species cross-section model (cross-section data / radius / ion Z).
std::vector< double > species_h0k
Per-species standard enthalpy of formation at 0 K [J/kmol]; forwarded to ReactorRHS::Config::species_...
double Te_min
Lower bound of the tabulated electron-temperature grid [K].
bool reacting
Include finite-rate chemistry source terms (true) or freeze composition (false) [-].
Definition ReactorRHS.h:63
CollisionModel collision
Composition-resolved sigma/nu_E (preferred).
Definition ReactorRHS.h:65
std::vector< double > species_h0k
Per-species standard enthalpy of formation at 0 K [J/kmol], length nSpecies(), in mechanism species-i...
Definition ReactorRHS.h:94
Physical constants and unit conversions, in SI by default.