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"
2#include "PlasmaRates.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 m_rhs = std::make_unique<ReactorRHS>(m_sol, std::move(rc));
54}
55
56std::vector<std::string> Plasma0DReactor::speciesNames() const
57{
58 std::vector<std::string> names(m_rhs->nSpecies());
59 for (std::size_t k = 0; k < names.size(); k++) names[k] = m_rhs->speciesName(k);
60 return names;
61}
62
63void Plasma0DReactor::rhs(double Tg, double Te, double V, const double* Y, double E,
64 double mass, double p_ext, double polytropic_index,
65 double* dydt) const
66{
67 // Constant-mass reactor: density is derived from the instantaneous volume rather
68 // than integrated directly (mirrors ConstantMassPlasmaReactorOde in Python).
69 const double rho = mass / V;
70 // dTg, dTe accumulate the RHS's energy-balance source terms [K/s]; lhsTg,
71 // lhsTe are the (heat-capacity-like) coefficients multiplying dTg/dt, dTe/dt
72 // on the LHS of the energy balance, so source terms below can be added as
73 // @f$ \text{source} / \text{lhs} @f$.
74 double dTg = 0.0, dTe = 0.0, lhsTg = 1.0, lhsTe = 1.0;
75 // dY goes straight into dydt[3..]; rates() leaves thermo at (Tg,Te,Y,rho).
76 // &m_diag caches the power/Maxwellian-condition breakdown for diagnostics().
77 m_rhs->rates(Tg, Te, Y, rho, E, dTg, dTe, dydt + 3, lhsTg, lhsTe, &m_diag);
78
79 double dV = 0.0;
80 if (std::isfinite(polytropic_index) && polytropic_index > 0.0 && m_gap > 0.0) {
81 // Polytropic rarefaction (mirrors ConstantMassPlasmaReactorOde): the hot
82 // core's overpressure relaxes toward p_ext over the acoustic (rarefaction)
83 // timescale tau derived below.
84 auto thermo = m_rhs->solution()->thermo();
85 const double P = thermo->pressure();
86 const double Xe = thermo->moleFraction(m_ie);
87 // Electron partial pressure [Pa], via the ideal-gas Dalton split
88 // @f$ p_e = X_e P @f$.
89 const double p_e = Xe * P;
90 // Clamp to avoid unphysical/singular values (e.g. at startup) feeding
91 // sqrt() below.
92 const double Tg_c = std::max(Tg, 200.0);
93 const double Te_c = std::max(Te, 200.0);
94 // Two-temperature mixture temperature (mole-fraction-weighted blend of Tg
95 // and Te), used as the effective temperature for the speed-of-sound
96 // estimate below:
97 // @f[
98 // \overline{T} = (1 - X_e) T_g + X_e T_e.
99 // @f]
100 const double Tmean = (1.0 - Xe) * Tg_c + Xe * Te_c;
101 const double cp = thermo->cp_mass();
102 const double cv = thermo->cv_mass();
103 // Fall back to diatomic-air @f$ \gamma @f$ if @f$ c_v \approx 0 @f$.
104 const double gamma = (cv > 0.0) ? cp / cv : 1.4;
105 const double M = thermo->meanMolecularWeight() * 1.0e-3; // kg/mol
106 // Treat the plasma volume as a cylinder of fixed axial length m_gap to
107 // recover an effective radius r, then the ideal-gas speed of sound at
108 // @f$ \overline{T} @f$ and the acoustic (rarefaction) timescale tau over
109 // which the core relaxes toward p_ext:
110 // @f[
111 // r = \sqrt{\frac{V}{\text{m\_gap} \, \pi}}, \qquad
112 // c_\text{sound} = \sqrt{\frac{\gamma R \overline{T}}{M}}, \qquad
113 // \tau = \frac{r}{c_\text{sound}}.
114 // @f]
115 const double radius = std::sqrt(V / (m_gap * units::pi));
116 const double c_sound = std::sqrt(gamma * units::R * Tmean / M);
117 const double tau = radius / c_sound;
118 // Polytropic volume equation (mirrors ConstantMassPlasmaReactorOde): the
119 // pressure relaxes toward p_ext over tau, and the volume responds via the
120 // polytropic relation with index n:
121 // @f[
122 // \frac{dp}{dt} = -\frac{P - p_\text{ext}}{\tau}, \qquad
123 // \frac{dV}{dt} = -\frac{1}{n} \frac{V}{P} \frac{dp}{dt}.
124 // @f]
125 const double dp_dt = -(P - p_ext) / tau;
126 dV = -1.0 / polytropic_index * (V / P) * dp_dt;
127 // Pressure work removes energy from each population as the cell expands.
128 if (std::isfinite(dV)) {
129 dTe -= (p_e * dV / V) / lhsTe;
130 dTg -= ((P - p_e) * dV / V) / lhsTg;
131 } else {
132 dV = 0.0;
133 }
134 }
135
136 dydt[0] = dTg;
137 dydt[1] = dTe;
138 dydt[2] = dV;
139 // dydt[3..] (dY) already written by rates().
140}
141
142} // 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).
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:61
CollisionModel collision
Composition-resolved sigma/nu_E (preferred).
Definition ReactorRHS.h:63
Physical constants and unit conversions, in SI by default.