41#include <pybind11/pybind11.h>
42#include <pybind11/stl.h>
43#include <pybind11/numpy.h>
72namespace py = pybind11;
84 std::vector<double> toVec(
const py::array_t<double> &a)
90 const py::array_t<double, py::array::c_style | py::array::forcecast> c(a);
91 return std::vector<double>(c.data(), c.data() + c.size());
102 std::vector<double> optVec(
const py::object &obj)
106 return toVec(obj.cast<py::array_t<double>>());
118 void requireLength(std::size_t size, std::size_t expected,
const char *name)
120 if (size != expected)
122 throw std::invalid_argument(
123 std::string(name) +
" has length " + std::to_string(size) +
124 ", expected " + std::to_string(expected) +
".");
134 void requireTeGrid(
int Te_n)
138 throw std::invalid_argument(
139 "Te_n must be at least 2 to span [Te_min, Te_max], got " +
140 std::to_string(Te_n) +
".");
154 void requireMatrixShape(
const py::array &a, std::size_t rows,
155 std::optional<std::size_t> cols,
const char *name)
157 const bool shape_ok =
158 a.ndim() == 2 &&
static_cast<std::size_t
>(a.shape(0)) == rows &&
159 (!cols ||
static_cast<std::size_t
>(a.shape(1)) == *cols);
162 std::string got =
"(";
163 for (py::ssize_t d = 0; d < a.ndim(); d++)
165 got += (d ?
", " :
"") + std::to_string(a.shape(d));
167 got += a.ndim() == 1 ?
",)" :
")";
168 throw std::invalid_argument(
169 std::string(name) +
" has shape " + got +
", expected (" +
170 std::to_string(rows) +
", " +
171 (cols ? std::to_string(*cols) : std::string(
"n_species")) +
").");
182 std::vector<int> toChargeNumbers(
const py::array_t<double> &a)
184 const std::vector<double> values = toVec(a);
185 std::vector<int> charges(values.size());
186 for (std::size_t k = 0; k < values.size(); k++)
188 if (!std::isfinite(values[k]) || values[k] != std::floor(values[k]))
190 throw std::invalid_argument(
191 "ion_Z[" + std::to_string(k) +
"] is " + std::to_string(values[k]) +
192 "; a charge number must be a whole number.");
194 charges[k] =
static_cast<int>(values[k]);
213 std::vector<rizer::CollisionModel::SpeciesSpec>
214 collisionSpecs(
const py::list &xsec_energy_J,
const py::list &xsec_sigma_m2,
215 const py::array_t<double> &radius_m,
const py::array_t<double> &ion_Z)
217 const std::size_t n_species =
static_cast<std::size_t
>(xsec_energy_J.size());
218 requireLength(
static_cast<std::size_t
>(xsec_sigma_m2.size()), n_species,
220 requireLength(
static_cast<std::size_t
>(radius_m.size()), n_species,
"radius_m");
221 requireLength(
static_cast<std::size_t
>(ion_Z.size()), n_species,
"ion_Z");
222 const std::vector<double> radius = toVec(radius_m);
223 const std::vector<int> charges = toChargeNumbers(ion_Z);
225 std::vector<rizer::CollisionModel::SpeciesSpec> specs(n_species);
226 for (std::size_t k = 0; k < n_species; k++)
228 specs[k].radius = radius[k];
229 specs[k].Z = charges[k];
230 const py::object energy = xsec_energy_J[k];
231 const py::object sigma = xsec_sigma_m2[k];
232 if (energy.is_none() != sigma.is_none())
234 throw std::invalid_argument(
235 "species " + std::to_string(k) +
" has " +
236 (energy.is_none() ?
"xsec_sigma_m2 but no xsec_energy_J"
237 :
"xsec_energy_J but no xsec_sigma_m2") +
238 "; a tabulated cross section needs both, or neither for a "
239 "hard sphere or no model.");
241 if (!energy.is_none())
243 specs[k].energy_J = toVec(energy.cast<py::array_t<double>>());
244 specs[k].sigma_m2 = toVec(sigma.cast<py::array_t<double>>());
269 const py::array_t<double> &values,
270 const char *name =
"property table")
272 std::vector<double> abscissa = toVec(T);
273 std::vector<double> ordinate = toVec(values);
274 if (abscissa.size() != ordinate.size())
276 throw std::invalid_argument(
278 ": the abscissa has " +
279 std::to_string(abscissa.size()) +
280 " point(s) against " +
281 std::to_string(ordinate.size()) +
282 " value(s); they tabulate one curve and must be the same length.");
284 for (std::size_t i = 1; i < abscissa.size(); i++)
286 if (!(abscissa[i] > abscissa[i - 1]))
288 throw std::invalid_argument(
290 ": the abscissa must be strictly increasing, but entry " +
292 " (" + std::to_string(abscissa[i]) +
293 ") does not exceed entry " +
294 std::to_string(i - 1) +
295 " (" + std::to_string(abscissa[i - 1]) +
").");
333 const std::string &phase)
337 std::filesystem::file_time_type modified;
338 std::unique_ptr<rizer::CanteraThermo> thermo;
340 static std::map<std::pair<std::string, std::string>, Entry> cache;
342 const auto modified = std::filesystem::last_write_time(path);
343 Entry &entry = cache[std::make_pair(path, phase)];
344 if (!entry.thermo || entry.modified != modified)
346 entry.thermo = std::make_unique<rizer::CanteraThermo>(path, phase);
347 entry.modified = modified;
349 return *entry.thermo;
361 "rizer Cantera plasma extension: a steady LTE arc column, a "
362 "transient two-temperature NRP plasma channel (0D and 1D radial) "
363 "with its explicit hydrodynamic Strang half-step, and the "
364 "thermodynamic accessors they share. Flat numerics only across "
365 "the boundary: no Cantera C++ object is ever exposed, so this "
366 "module and the stock `cantera` package coexist in one process.";
368 m.def(
"cantera_version", []()
369 {
return Cantera::version(); },
"Version string of the linked libcantera.");
379 m.attr(
"has_openmp") =
true;
381 m.attr(
"has_openmp") =
false;
385 "(Re-)register the native C++ plasma reaction rates "
386 "(janev-dissociative-recombination-C2Hy, Druyvesteyn, "
387 "reverse-two-temperature-plasma) with Cantera's ReactionRateFactory. "
388 "Idempotent; replaces the Python ExtensibleRate registrations of the "
389 "same names, so mechanisms load and evaluate natively afterwards.");
391 m.def(
"mean_cross_section",
401 py::array_t<double> energy_J,
403 py::array_t<double> sigma_m2,
405 py::array_t<double> Te,
420 std::vector<double> e = toVec(energy_J);
421 std::vector<double> s = toVec(sigma_m2);
422 std::vector<double> t = toVec(Te);
426 if (e.size() != s.size()) {
427 throw std::invalid_argument(
428 "mean_cross_section: energy_J has "
429 + std::to_string(e.size()) +
" entries but sigma_m2 has "
430 + std::to_string(s.size()) +
"; they tabulate the same "
431 "cross section and must be the same length.");
434 throw std::invalid_argument(
435 "mean_cross_section: energy_J/sigma_m2 need at least 2 "
436 "points to interpolate, got " + std::to_string(e.size())
439 std::vector<double> q =
441 return py::array_t<double>(q.size(), q.data()); },
443 py::arg(
"energy_J"), py::arg(
"sigma_m2"), py::arg(
"Te"),
445 py::arg(
"x_max") = 20.0, py::arg(
"N") = 1000);
450 py::class_<rizer::PlasmaReactor0D>(m,
"Reactor0D")
462 const std::string &mech,
464 const std::string &phase,
471 py::list xsec_energy_J,
474 py::list xsec_sigma_m2,
477 py::array_t<double> radius_m,
479 py::array_t<double> ion_Z,
492 py::array_t<double> species_h0k
506 cfg.
Te_n =
static_cast<std::size_t
>(Te_n);
509 cfg.
specs = collisionSpecs(xsec_energy_J, xsec_sigma_m2, radius_m,
511 return std::make_unique<rizer::PlasmaReactor0D>(cfg);
517 py::arg(
"reacting") =
true,
519 py::arg(
"xsec_energy_J"),
520 py::arg(
"xsec_sigma_m2"),
524 py::arg(
"Te_min") = 200.0,
525 py::arg(
"Te_max") = 1.0e5,
526 py::arg(
"Te_n") = 400,
528 py::arg(
"gap") = 0.0,
529 py::arg(
"species_h0k") = py::array_t<double>(0))
531 .def(
"electrical_conductivity",
542 py::array_t<double> Y,
554 requireLength(y.size(), self.
nSpecies(),
"Y");
557 py::arg(
"Tg"), py::arg(
"Te"), py::arg(
"Y"), py::arg(
"rho"))
559 .def(
"electron_density",
570 py::array_t<double> Y,
582 requireLength(y.size(), self.
nSpecies(),
"Y");
585 py::arg(
"Tg"), py::arg(
"Te"), py::arg(
"Y"), py::arg(
"rho"))
600 py::array_t<double> Y,
608 double polytropic_index
619 requireLength(y.size(), self.
nSpecies(),
"Y");
620 std::vector<double> dydt(3 + self.
nSpecies(), 0.0);
621 self.
rhs(Tg, Te, V, y.data(), E, mass, p_ext, polytropic_index,
623 return py::array_t<double>(dydt.size(), dydt.data()); },
625 py::arg(
"Tg"), py::arg(
"Te"), py::arg(
"V"), py::arg(
"Y"), py::arg(
"E"), py::arg(
"mass"), py::arg(
"p_ext"), py::arg(
"polytropic_index"))
652 m.def(
"solve_column",
665 double electric_field,
671 py::array_t<double> sigma_T,
673 py::array_t<double> sigma_v,
675 py::array_t<double> kappa_T,
677 py::array_t<double> kappa_v,
687 std::size_t n_points,
690 double T_center_guess,
736 makeTable(sigma_T, sigma_v,
"solve_column: sigma_T/sigma_v");
738 makeTable(kappa_T, kappa_v,
"solve_column: kappa_T/kappa_v");
743 (prad_T.is_none() || prad_v.is_none())
745 : makeTable(prad_T.cast<py::array_t<double>>(),
746 prad_v.cast<py::array_t<double>>(),
747 "solve_column: prad_T/prad_v");
765 opts.
init_r = optVec(init_r);
766 opts.
init_T = optVec(init_T);
772 py::gil_scoped_release release;
774 sigma, kappa, prad, opts);
779 out[
"r"] = py::array_t<double>(res.
r.size(), res.
r.data());
780 out[
"T"] = py::array_t<double>(res.
T.size(), res.
T.data());
781 out[
"sigma"] = py::array_t<double>(res.
sigma.size(), res.
sigma.data());
782 out[
"kappa"] = py::array_t<double>(res.
kappa.size(), res.
kappa.data());
788 py::arg(
"R"), py::arg(
"electric_field"), py::arg(
"T_wall"),
790 py::arg(
"sigma_T"), py::arg(
"sigma_v"), py::arg(
"kappa_T"), py::arg(
"kappa_v"),
792 py::arg(
"prad_T") = py::none(), py::arg(
"prad_v") = py::none(),
794 py::arg(
"n_points") = 41, py::arg(
"T_center_guess") = 10000.0, py::arg(
"init_r") = py::none(), py::arg(
"init_T") = py::none(),
796 py::arg(
"rho_cp") = 1.0e3, py::arg(
"loglevel") = 0,
798 py::arg(
"refine_grid") =
true, py::arg(
"refine_ratio") = 4.0, py::arg(
"refine_slope") = 0.05, py::arg(
"refine_curve") = 0.10, py::arg(
"refine_prune") = 0.0);
800 m.def(
"solve_1D_unsteady_discharge",
816 const std::string &mech,
818 const std::string &phase,
822 std::size_t n_points,
838 py::array_t<double> Y0,
860 py::object init_rho_r,
863 py::object init_rho_v,
866 py::object init_ur_r,
869 py::object init_ur_v,
877 py::object species_h0k,
883 double electric_field,
895 py::object plasma_voltage,
921 py::object kappa_H_T,
924 py::object kappa_H_v,
927 py::object kappa_e_T,
930 py::object kappa_e_v,
938 py::object xsec_energy_J,
942 py::object xsec_sigma_m2,
972 const std::string &outer_bc_mode,
983 double hydro_substep,
995 std::size_t record_every,
1009 bool independent_nodes,
1013 std::size_t n_threads,
1034 auto& cfg = options.
cfg;
1037 cfg.n_points = n_points;
1040 cfg.electric_field = electric_field;
1042 cfg.D_species = D_species;
1043 cfg.reacting = reacting;
1045 cfg.species_h0k = optVec(species_h0k);
1046 cfg.sigma_v = optVec(sigma_v);
1047 cfg.sigma_T = optVec(sigma_T);
1048 cfg.kappa_H_v = optVec(kappa_H_v);
1049 cfg.kappa_H_T = optVec(kappa_H_T);
1050 cfg.kappa_e_v = optVec(kappa_e_v);
1051 cfg.kappa_e_T = optVec(kappa_e_T);
1053 cfg.grid = optVec(grid);
1060 const double outer_radius = cfg.grid.size() >= 2 ? cfg.grid.back() : R_max;
1061 cfg.initTg_v = initTg_v.is_none() ? std::vector<double>{Tg0, Tg0}
1063 cfg.initTg_r = initTg_r.is_none() ? std::vector<double>{0.0, outer_radius}
1065 cfg.initTe_v = initTe_v.is_none() ? std::vector<double>{Te0, Te0}
1067 cfg.initTe_r = initTe_r.is_none() ? std::vector<double>{0.0, outer_radius}
1069 if (initY_v.is_none()) {
1071 const std::vector<double> Y0_v = toVec(Y0);
1072 cfg.initY_r = {0.0, outer_radius};
1074 cfg.initY_v.insert(cfg.initY_v.end(), Y0_v.begin(), Y0_v.end());
1078 cfg.initY_r = optVec(initY_r);
1079 const auto initY_array = initY_v.cast<py::array_t<double>>();
1080 requireMatrixShape(initY_array, cfg.initY_r.size(),
1081 static_cast<std::size_t
>(Y0.size()),
"initY_v");
1082 cfg.initY_v = toVec(initY_array);
1086 cfg.nu_m_T = optVec(nu_m_T);
1087 cfg.nu_m_v = optVec(nu_m_v);
1089 options.
Et_t = optVec(Efield_t);
1090 options.
Et_v = optVec(Efield_v);
1091 cfg.init_rho_r = optVec(init_rho_r);
1092 cfg.init_rho_v = optVec(init_rho_v);
1093 cfg.hydrodynamics = hydrodynamics;
1094 cfg.init_ur_r = optVec(init_ur_r);
1095 cfg.init_ur_v = optVec(init_ur_v);
1096 cfg.outer_bc_mode = outer_bc_mode;
1099 cfg.hydro_substep = hydro_substep;
1105 const bool any_collision_arg = !xsec_energy_J.is_none()
1106 || !xsec_sigma_m2.is_none() || !radius_m.is_none() || !ion_Z.is_none();
1107 if (any_collision_arg) {
1108 if (xsec_energy_J.is_none() || xsec_sigma_m2.is_none()
1109 || radius_m.is_none() || ion_Z.is_none()) {
1110 throw std::invalid_argument(
1111 "solve_1D_unsteady_discharge: xsec_energy_J, xsec_sigma_m2, "
1112 "radius_m, and ion_Z must all be given together (or all "
1113 "omitted) to enable the composition-resolved collision "
1116 cfg.specs = collisionSpecs(xsec_energy_J.cast<py::list>(),
1117 xsec_sigma_m2.cast<py::list>(),
1118 radius_m.cast<py::array_t<double>>(),
1119 ion_Z.cast<py::array_t<double>>());
1120 cfg.Te_min = Te_min;
1121 cfg.Te_max = Te_max;
1122 requireTeGrid(Te_n);
1123 cfg.Te_n =
static_cast<std::size_t
>(Te_n);
1130 options.
rtol = rtol;
1135 if (Y_amb.is_none()) {
1136 throw std::invalid_argument(
1137 "solve_1D_unsteady_discharge: Y_amb is required. It is the "
1138 "composition OUTSIDE the domain -- the wall ghost and the "
1139 "hydro inflow read it -- and it is deliberately not derived "
1140 "from Y0, which is the composition INSIDE at t=0. Pass "
1141 "Y_amb=Y0 only when the boundary genuinely holds the "
1142 "initial composition.");
1144 cfg.Y_amb = toVec(Y_amb.cast<py::array_t<double>>());
1146 if (!plasma_voltage.is_none()) {
1151 py::function callback = plasma_voltage;
1153 [callback](
double t,
double R_p) ->
double {
1154 py::gil_scoped_acquire gil;
1155 return callback(t, R_p).cast<
double>();
1164 py::gil_scoped_release release;
1169 out[
"t"] = py::array_t<double>(history.
t.size(), history.
t.data());
1170 out[
"r"] = py::array_t<double>(history.
r.size(), history.
r.data());
1172 out[
"Tg"] = py::array_t<double>(
1174 out[
"Te"] = py::array_t<double>(
1176 out[
"ne"] = py::array_t<double>(
1178 out[
"Y"] = py::array_t<double>(
1180 out[
"rho"] = py::array_t<double>(
1182 out[
"u_r"] = py::array_t<double>(
1184 out[
"p"] = py::array_t<double>(
1187 out[
"boundary_species_mass_out"] = py::array_t<double>(
1191 out[
"boundary_electron_energy_out"] =
1194 out[
"boundary_conduction_energy_out"] =
1196 out[
"boundary_diffusion_energy_out"] =
1199 for (
const auto& [key, values] : {
1203 std::pair{
"solver_err_test_fails",
1205 std::pair{
"solver_nonlin_conv_fails",
1208 std::pair{
"solver_lin_rhs_evals",
1212 out[key] = py::array_t<double>(values->size(), values->data());
1215 for (
const auto& [key, values] : {
1216 std::pair{
"Rp", &history.
Rp},
1217 std::pair{
"Rp_state", &history.
Rp_state},
1218 std::pair{
"Vp", &history.
Vp},
1219 std::pair{
"I", &history.
I},
1220 std::pair{
"E", &history.
E},
1222 out[key] = py::array_t<double>(values->size(), values->data());
1224 out[
"species"] = history.
species;
1225 out[
"nframes"] = history.
nframes;
1226 out[
"npts"] = history.
npts;
1227 out[
"nsp"] = history.
nsp;
1230 py::arg(
"mech"), py::arg(
"phase"),
1232 py::arg(
"n_points"), py::arg(
"R_max"),
1234 py::arg(
"grid") = py::none(),
1236 py::arg(
"rho"), py::arg(
"Tg0"), py::arg(
"Te0"), py::arg(
"Y0"),
1238 py::arg(
"initTg_r") = py::none(), py::arg(
"initTg_v") = py::none(), py::arg(
"initTe_r") = py::none(), py::arg(
"initTe_v") = py::none(), py::arg(
"initY_r") = py::none(), py::arg(
"initY_v") = py::none(), py::arg(
"init_rho_r") = py::none(), py::arg(
"init_rho_v") = py::none(), py::arg(
"init_ur_r") = py::none(), py::arg(
"init_ur_v") = py::none(),
1240 py::arg(
"reacting") =
true, py::arg(
"species_h0k") = py::none(),
1242 py::arg(
"electric_field") = 0.0, py::arg(
"Efield_t") = py::none(), py::arg(
"Efield_v") = py::none(), py::arg(
"gap") = 0.0, py::arg(
"plasma_voltage") = py::none(),
1244 py::arg(
"sigma_T") = py::none(), py::arg(
"sigma_v") = py::none(), py::arg(
"nu_E") = 0.0, py::arg(
"nu_m") = 0.0, py::arg(
"nu_m_T") = py::none(), py::arg(
"nu_m_v") = py::none(), py::arg(
"kappa_H_T") = py::none(), py::arg(
"kappa_H_v") = py::none(), py::arg(
"kappa_e_T") = py::none(), py::arg(
"kappa_e_v") = py::none(), py::arg(
"D_species") = 0.0,
1246 py::arg(
"xsec_energy_J") = py::none(), py::arg(
"xsec_sigma_m2") = py::none(), py::arg(
"radius_m") = py::none(), py::arg(
"ion_Z") = py::none(), py::arg(
"Te_min") = 200.0, py::arg(
"Te_max") = 1.0e5, py::arg(
"Te_n") = 400,
1248 py::arg(
"T_amb") = 300.0, py::arg(
"p_amb") = -1.0, py::arg(
"Y_amb") = py::none(), py::arg(
"outer_bc_mode") =
"ambient_ghost",
1250 py::arg(
"hydrodynamics") =
false, py::arg(
"cfl") = 0.9, py::arg(
"hydro_substep") = 0.0,
1252 py::arg(
"dt") = 1.0e-9, py::arg(
"n_steps") = 1000, py::arg(
"record_every") = 1,
1254 py::arg(
"rtol") = 1.0e-7, py::arg(
"atol_T") = 1.0e-3, py::arg(
"atol_Y") = 1.0e-9, py::arg(
"independent_nodes") =
false, py::arg(
"n_threads") = 1, py::arg(
"loglevel") = 0);
1257 m.def(
"hydro_thermo_probe",
1266 const std::string &mech,
1268 const std::string &phase,
1276 py::array_t<double> Y,
1291 const std::vector<double> Yv = toVec(Y);
1293 if (Yv.size() != thermo.
nSpecies()) {
1294 throw std::invalid_argument(
1295 "hydro_thermo_probe: Y has length "
1296 + std::to_string(Yv.size()) +
", expected "
1297 + std::to_string(thermo.
nSpecies()) +
".");
1299 double p_heavy = 0.0;
1300 double p_electron = 0.0;
1301 thermo.
pressures(0.0, 0.0, rho, Tg, Te, Yv.data(), p_heavy, p_electron);
1302 const double heavy_energy = thermo.
heavyEnergy(Tg, Yv.data());
1304 Tg_seed.is_none() ? Tg : Tg_seed.cast<
double>();
1307 out[
"p_heavy"] = p_heavy;
1308 out[
"p_electron"] = p_electron;
1309 out[
"sound_speed"] =
1310 thermo.
soundSpeed(0.0, 0.0, rho, Tg, Te, Yv.data());
1311 out[
"heavy_energy"] = heavy_energy;
1314 p_heavy + p_electron, Tg, Te, Yv.data());
1315 out[
"heavy_temperature"] =
1319 py::arg(
"mech"), py::arg(
"phase"), py::arg(
"rho"), py::arg(
"Tg"), py::arg(
"Te"), py::arg(
"Y"),
1321 py::arg(
"Tg_seed") = py::none());
1324 m.def(
"radial_diffusion_probe",
1340 const std::string &mech,
1342 const std::string &phase,
1344 py::array_t<double> grid,
1346 py::array_t<double> rho,
1348 py::array_t<double> Tg,
1350 py::array_t<double> Te,
1352 py::array_t<double> Y,
1357 py::array_t<double> Y_amb,
1359 double wall_ghost_density,
1361 py::object kappa_H_T,
1365 py::object kappa_H_v,
1368 py::object kappa_e_T,
1371 py::object kappa_e_v,
1386 const auto g = toVec(grid);
1387 const auto rho_v = toVec(rho);
1388 const auto Tg_v = toVec(Tg);
1389 const auto Te_v = toVec(Te);
1390 const auto Y_v = toVec(Y);
1391 const auto Y_amb_v = toVec(Y_amb);
1392 const std::size_t n_points = g.size();
1394 throw std::invalid_argument(
1395 "radial_diffusion_probe: the grid needs at least 2 nodes.");
1397 requireLength(rho_v.size(), n_points,
"rho");
1398 requireLength(Tg_v.size(), n_points,
"Tg");
1399 requireLength(Te_v.size(), n_points,
"Te");
1400 requireMatrixShape(Y, n_points, Y_amb_v.size(),
"Y");
1407 cfg.
R_max = g.back();
1408 cfg.
rho = rho_v.front();
1414 cfg.
initTg_r = {g.front(), g.back()};
1415 cfg.
initTg_v = {Tg_v.front(), Tg_v.front()};
1416 cfg.
initTe_r = {g.front(), g.back()};
1417 cfg.
initTe_v = {Te_v.front(), Te_v.front()};
1418 const std::size_t n_species_in = Y_amb_v.size();
1419 if (Y_v.size() != n_points * n_species_in) {
1420 throw std::invalid_argument(
1421 "radial_diffusion_probe: Y has " + std::to_string(Y_v.size()) +
1422 " entries, expected " + std::to_string(n_points * n_species_in) +
1423 " (one row of Y_amb's length per grid node).");
1425 cfg.
initY_r = {g.front(), g.back()};
1426 cfg.
initY_v.assign(Y_v.begin(), Y_v.begin() + n_species_in);
1427 cfg.
initY_v.insert(cfg.
initY_v.end(), Y_v.end() - n_species_in, Y_v.end());
1428 cfg.
Y_amb = Y_amb_v;
1438 const std::size_t n_species = channel.
nSpecies();
1439 if (Y_v.size() != n_points * n_species) {
1440 throw std::invalid_argument(
1441 "radial_diffusion_probe: Y has " + std::to_string(Y_v.size()) +
1442 " entries, expected " + std::to_string(n_points * n_species) +
".");
1444 channel.
setHydroState(rho_v, std::vector<double>(n_points, 0.0));
1448 const std::size_t n_vars = 2 + n_species;
1449 std::vector<double> x(n_vars * n_points, 0.0);
1450 for (std::size_t j = 0; j < n_points; j++) {
1451 x[channel.
index(0, j)] = Tg_v[j];
1452 x[channel.
index(1, j)] = Te_v[j];
1453 for (std::size_t k = 0; k < n_species; k++) {
1454 x[channel.
index(2 + k, j)] = Y_v[j * n_species + k];
1464 coefficients.
Y_amb = &Y_amb_v;
1470 std::vector<double> dTg(n_points, 0.0), dTe(n_points, 0.0);
1471 std::vector<double> dY(n_points * n_species, 0.0);
1472 std::vector<double> scratch(n_species, 0.0);
1473 for (std::size_t j = 0; j < n_points; j++) {
1479 &Y_v[j * n_species], rho_v[j]);
1482 coefficients, j, 1.0, 1.0, dTg[j], dTe[j],
1483 &dY[j * n_species], scratch.data());
1487 out[
"dTg"] = py::array_t<double>(dTg.size(), dTg.data());
1488 out[
"dTe"] = py::array_t<double>(dTe.size(), dTe.data());
1489 out[
"dY"] = py::array_t<double>(
1490 {
static_cast<py::ssize_t
>(n_points),
1491 static_cast<py::ssize_t
>(n_species)},
1504 py::arg(
"wall_ghost_density"),
1506 py::arg(
"kappa_H_T") = py::none(),
1507 py::arg(
"kappa_H_v") = py::none(),
1508 py::arg(
"kappa_e_T") = py::none(),
1509 py::arg(
"kappa_e_v") = py::none(),
1510 py::arg(
"D_species") = 0.0);
1512 m.def(
"solve_hydro_step_only",
1531 py::array_t<double> r_node,
1533 py::array_t<double> r_face,
1537 py::array_t<double> V_cell,
1540 bool cylindrical_source,
1546 py::array_t<double> rho,
1548 py::array_t<double> u_r,
1550 py::array_t<double> Tg,
1552 py::array_t<double> Te,
1554 py::array_t<double> Y,
1558 const std::string &eos,
1589 py::array_t<double> molecular_weights,
1597 py::object prescribed_p,
1600 const std::string &mech,
1603 const std::string &phase,
1608 const std::string &outer_bc_mode,
1619 py::array_t<double> Y_amb,
1623 py::object ghost_state,
1638 std::size_t n_subcycles,
1661 cfg.
r_node = toVec(r_node);
1662 cfg.
r_face = toVec(r_face);
1663 cfg.
V_cell = toVec(V_cell);
1667 cfg.
Y_amb = toVec(Y_amb);
1677 state.
rho = toVec(rho);
1678 state.
u_r = toVec(u_r);
1679 state.
Tg = toVec(Tg);
1680 state.
Te = toVec(Te);
1682 const std::size_t n_points = state.
rho.size();
1683 if (n_points == 0) {
1684 throw std::invalid_argument(
1685 "solve_hydro_step_only: rho is empty.");
1687 requireMatrixShape(Y, n_points, std::nullopt,
"Y");
1688 state.
n_species =
static_cast<std::size_t
>(Y.shape(1));
1694 const std::size_t n_weights =
1695 static_cast<std::size_t
>(molecular_weights.size());
1696 if (eos !=
"cantera" && n_weights != state.
n_species) {
1697 throw std::invalid_argument(
1698 "solve_hydro_step_only: molecular_weights has "
1699 + std::to_string(n_weights) +
" entries but Y carries "
1701 +
" species per node.");
1706 std::unique_ptr<rizer::ThermoAccessor> owned_thermo;
1708 if (eos ==
"cantera") {
1709 if (mech.empty() || phase.empty()) {
1710 throw std::invalid_argument(
1711 "solve_hydro_step_only: eos=\"cantera\" needs mech and "
1712 "phase; the equation of state comes from the mechanism, "
1713 "not from gamma and molecular_weights.");
1717 throw std::invalid_argument(
1718 "solve_hydro_step_only: Y carries "
1720 +
" species per node but phase \"" + phase
1721 +
"\" has " + std::to_string(cantera.
nSpecies())
1724 if (electron_index < 0) {
1731 throw std::invalid_argument(
1732 "solve_hydro_step_only: eos=\"cantera\" needs a real "
1733 "electron_index; -1 skips the electron energy row while "
1734 "the Cantera accessor keeps the electron out of the "
1735 "heavy pool, which drops its energy from both rows.");
1737 if (
static_cast<std::size_t
>(electron_index) != cantera.
electronIndex()) {
1743 throw std::invalid_argument(
1744 "solve_hydro_step_only: electron_index = "
1745 + std::to_string(electron_index)
1746 +
" but phase \"" + phase +
"\" has its electron at index "
1750 }
else if (eos ==
"gamma") {
1751 owned_thermo = std::make_unique<rizer::GammaLawThermo>(
1752 gamma, toVec(molecular_weights), electron_index);
1753 thermo = owned_thermo.get();
1754 }
else if (eos ==
"prescribed_p") {
1755 if (prescribed_p.is_none()) {
1756 throw std::invalid_argument(
1757 "solve_hydro_step_only: eos=\"prescribed_p\" needs a "
1758 "prescribed_p callable p(r, t) -> Pa.");
1760 if (ghost_state.is_none()) {
1767 throw std::invalid_argument(
1768 "solve_hydro_step_only: eos=\"prescribed_p\" needs a "
1769 "ghost_state callback. outer_bc_mode closes the ghost "
1770 "density from p_amb through the gamma-law backing, "
1771 "which does not invert the prescribed p(r, t).");
1773 auto fn = prescribed_p.cast<py::function>();
1774 owned_thermo = std::make_unique<rizer::PrescribedPressureThermo>(
1775 [fn](
double r,
double t) {
1776 return fn(r, t).cast<
double>();
1780 thermo = owned_thermo.get();
1782 throw std::invalid_argument(
1783 "solve_hydro_step_only: eos must be \"gamma\", \"cantera\" "
1784 "or \"prescribed_p\", got \"" + eos +
"\".");
1788 if (!ghost_state.is_none()) {
1789 auto fn = ghost_state.cast<py::function>();
1790 const std::size_t nsp = state.
n_species;
1791 cfg.
ghost_state = [fn, nsp](
double r_ghost,
double t,
1794 py::object out = fn(
1795 r_ghost, t, is_inner_face, interior.rho,
1796 interior.u_r, interior.Tg, interior.Te,
1797 py::array_t<double>(interior.Y.size(),
1798 interior.Y.data()));
1799 auto tup = out.cast<py::tuple>();
1800 if (tup.size() != 5) {
1801 throw std::invalid_argument(
1802 "solve_hydro_step_only: ghost_state must return "
1803 "(rho, u_r, Tg, Te, Y).");
1806 ghost.
rho = tup[0].cast<
double>();
1807 ghost.
u_r = tup[1].cast<
double>();
1808 ghost.
Tg = tup[2].cast<
double>();
1809 ghost.
Te = tup[3].cast<
double>();
1810 ghost.
Y = toVec(tup[4].cast<py::array_t<double>>());
1811 if (ghost.
Y.size() != nsp) {
1812 throw std::invalid_argument(
1813 "solve_hydro_step_only: ghost_state returned a Y "
1814 "of the wrong length.");
1823 out[
"rho"] = py::array_t<double>(state.
rho.size(), state.
rho.data());
1824 out[
"u_r"] = py::array_t<double>(state.
u_r.size(), state.
u_r.data());
1825 out[
"Tg"] = py::array_t<double>(state.
Tg.size(), state.
Tg.data());
1826 out[
"Te"] = py::array_t<double>(state.
Te.size(), state.
Te.data());
1827 out[
"Y"] = py::array_t<double>(
1828 std::vector<py::ssize_t>{
static_cast<py::ssize_t
>(n_points),
1829 static_cast<py::ssize_t
>(state.
n_species)},
1832 out[
"dt_cfl"] = result.
dt_cfl;
1833 out[
"rho_min"] = result.
rho_min;
1834 out[
"p_min"] = result.
p_min;
1836 out[
"boundary_species_mass_out"] = py::array_t<double>(
1840 out[
"boundary_electron_energy_out"] =
1845 py::arg(
"r_node"), py::arg(
"r_face"), py::arg(
"V_cell"),
1847 py::arg(
"cylindrical_source") =
true,
1849 py::arg(
"rho"), py::arg(
"u_r"), py::arg(
"Tg"), py::arg(
"Te"), py::arg(
"Y"),
1851 py::arg(
"eos") =
"gamma", py::arg(
"gamma") = 1.4, py::arg(
"molecular_weights") = py::array_t<double>(0), py::arg(
"electron_index") = -1, py::arg(
"prescribed_p") = py::none(), py::arg(
"mech") =
"", py::arg(
"phase") =
"",
1853 py::arg(
"outer_bc_mode") =
"ambient_ghost", py::arg(
"p_amb") = 0.0, py::arg(
"T_amb") = 0.0, py::arg(
"Y_amb") = py::array_t<double>(0), py::arg(
"ghost_state") = py::none(),
1855 py::arg(
"dt_total"),
1857 py::arg(
"n_subcycles") = 0, py::arg(
"cfl") = 0.9, py::arg(
"t0") = 0.0);
Outer-boundary vocabulary for the 1D radial channel: which treatment is applied, the primitive state ...
The production ThermoAccessor for the hydro sub-step: Cantera's two-temperature PlasmaPhase behind Hy...
Calorically perfect ThermoAccessor: one constant gamma for the heavy pool, ElectronGamma for the elec...
Standalone explicit hydrodynamic sub-stepper for the 1D radial two-temperature plasma channel: the "A...
The transient two-temperature radial channel as a Cantera Domain1D: per-node source terms from Plasma...
Drives PlasmaChannel1D in time and records what it did.
The standalone 0-D two-temperature reactor: PlasmaCell plus the constant-mass volume equation,...
TEST-ONLY ThermoAccessor holding the pressure to a prescribed field p(r, t) and delegating every othe...
The parabolic half of PlasmaChannel1D::eval(): conduction in both pools, Fickian species diffusion,...
The equation-of-state seam of the hydrodynamic sub-step: the abstract accessor hydroStep() closes its...
PYBIND11_MODULE(_plasma1d, m)
size_t index(size_t n, size_t j) const
ThermoAccessor backed by a PlasmaPhase.
std::size_t electronIndex() const
Index of the electron species, the one heavyEnergy() leaves out.
double electronCv() const override
exactly: no thermo lookup and no state, so it is the same number at every node and every step.
double heavyEnergy(double Tg, const double *Y) const override
Cantera's mixture internal energy less the electron's contribution, on the per kg of MIXTURE basis th...
double soundSpeed(double r, double t, double rho, double Tg, double Te, const double *Y) const override
summed over the heavy species alone, from the fixed ElectronGamma, over the same partial-pressure sp...
double densityFromPressure(double pressure, double Tg, double Te, const double *Y) const override
PlasmaPhase::setPressure() read back through density().
double heavyTemperature(double rho, const double *Y, double heavy_internal_energy_target, double Tg_seed) const override
Damped Newton on = target, seeded with Tg_seed and confined to [200 K, 1e5 K].
std::size_t nSpecies() const
Number of species in the loaded phase.
void pressures(double r, double t, double rho, double Tg, double Te, const double *Y, double &p_heavy, double &p_electron) const override
PlasmaPhase::electronPressure(), and the total less that.
static std::vector< double > meanCrossSectionTable(const std::vector< double > &energy_J, const std::vector< double > &sigma_m2, const std::vector< double > &Te, double x_max=20.0, int N=1000)
Tabulated variant of meanCrossSection, evaluated at each Te in the input vector.
Calorically perfect ideal-gas accessor: one constant for the heavy pool, ElectronGamma for the elect...
Transient, two-temperature (Te != Tg), finite-rate reacting 1-D radial plasma channel (custom Cantera...
const PropertyTable & electronConductivity() const
std::size_t nSpecies() const
Number of species K [-] in this domain's mechanism.
void setHydroState(const std::vector< double > &rho, const std::vector< double > &u_r)
Replace the fields the hydro sub-step owns.
std::size_t electronIndex() const
Species index of the electron [-]; the constructor rejects a mechanism without one,...
const std::vector< double > & molecularWeights() const
const PropertyTable & heavyConductivity() const
The transport tables and per-species properties the diffusion operator reads.
void updateSpeciesCp(double Tg, double Te, const double *Y, double rho) const
Refresh speciesCp() at one node's state.
const std::vector< double > & speciesCp() const
Per-species [J/kg/K], length nSpecies(), at the state the cell was last evaluated at.
Native C++ two-temperature 0D plasma reactor (stateless per-eval evaluator).
double electronDensity(double Tg, double Te, const double *Y, double rho) const
Electron number density [1/m^3].
std::size_t nSpecies() 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.
std::vector< std::string > speciesNames() const
Species names in mechanism index order, so a caller passing plain numbers across the Python boundary ...
const PlasmaCell::Diagnostics & diagnostics() const
Power / Maxwellian-condition breakdown from the most recent rhs() call (see PlasmaCell::Diagnostics) ...
double electricalConductivity(double Tg, double Te, const double *Y, double rho) const
Electrical conductivity [S/m] at state .
static PropertyTable constant(double value)
A two-point constant table returning value for any T.
Equation-of-state and caloric accessor injected into hydroStep().
OuterBc parseOuterBc(const std::string &mode)
Map an outer_bc_mode config string onto the enum.
ChannelHistory solveChannelTransient(const ChannelOptions &opts)
Build a PlasmaChannel1D from opts.cfg, advance it from t=0 to [s] with the stiff BDF backend,...
void addRadialDiffusion(const PlasmaChannel1D &domain, Cantera::span< const double > x, const RadialDiffusionCoefficients &c, std::size_t j, double lhsTg, double lhsTe, double &dTg, double &dTe, double *dYdt, double *scratch)
Add one node's diffusive contributions to the rates the caller holds.
HydroStepResult hydroStep(const HydroStepConfig &cfg, HydroStepState &state)
Advance state over one Strang half-step of cfg.dt_total.
void registerPlasmaRates()
Register the custom plasma reaction rates with Cantera's ReactionRateFactory.
ColumnResult solveColumn(double R, double electric_field, double T_wall, const PropertyTable &sigma, const PropertyTable &kappa, const PropertyTable &p_rad, const ColumnOptions &opts)
Build, solve, and extract the radial Elenbaas-Heller column.
double pressure_work_electron
std::vector< double > I
Total current V_p/R_p [A].
std::vector< double > Vp
Plasma terminal voltage [V].
std::vector< double > ne
Species mass fractions [-], row-major [nframes, npts, nsp].
std::vector< double > E
Field V_p/gap [V/m].
std::vector< double > Rp_state
Resistance of the recorded state [Ohm].
std::size_t npts
Number of radial grid points [-].
std::vector< double > boundary_species_mass_out
The same, per species [kg/m/rad], length nsp, with what species diffusion carries through the wall gh...
std::vector< double > u_r
Species names, length nsp (order matches the Y axis).
std::vector< double > t
< Recorded times [s], length nframes
std::vector< double > solver_err_test_fails
std::vector< double > solver_last_order
double boundary_conduction_energy_out
What conduction (both pools) and species diffusion moved out through the wall ghost face over the run...
std::vector< double > p
Radial velocity [m/s], row-major [nframes, npts].
std::vector< std::string > species
std::size_t nsp
Number of species [-].
std::vector< double > solver_jac_evals
std::vector< double > Y
Mass density [kg/m^3], row-major [nframes, npts].
std::vector< double > solver_rhs_evals
std::vector< double > r
Gas temperature [K], row-major [nframes, npts].
std::vector< double > solver_lin_setups
std::size_t nframes
Number of recorded time frames [-].
std::vector< double > solver_steps
Per-frame CVODE counters, length nframes; the t=0 entry is zero, since nothing has been integrated ye...
double boundary_total_energy_out
std::vector< double > Rp
Circuit history, length nframes when ChannelOptions::plasma_voltage is set and empty otherwise.
std::vector< double > Tg
Electron temperature [K], row-major [nframes, npts].
std::vector< double > Te
Electron number density [1/m^3], row-major [nframes, npts].
std::vector< double > rho
Total pressure [Pa], row-major [nframes, npts].
double boundary_mass_out
What the hydrodynamic sub-step moved across the domain boundary, accumulated over every half-step of ...
std::vector< double > solver_nonlin_conv_fails
double boundary_electron_energy_out
double boundary_diffusion_energy_out
std::vector< double > solver_lin_rhs_evals
RHS calls made by the linear solver itself, forming the banded Jacobian by difference quotients.
double rtol
Error tolerances for the stiff BDF backend, which sub-steps internally until it meets them,...
std::function< double(double, double)> plasma_voltage
Closes the electrical circuit on the channel's own resistance, given and returning the plasma termin...
int loglevel
Diagnostic verbosity [-] (0 = silent; >0 prints progress).
std::size_t n_threads
Threads advancing the per-node systems of independent_nodes.
double atol_T
Absolute tolerance on Tg and Te [K].
double gap
Inter-electrode gap [m].
std::size_t n_steps
Number of output steps [-] (dt*n_steps = total time [s]).
PlasmaChannelConfig cfg
Domain physics + initial/boundary state.
std::vector< double > Et_t
Field-vs-time schedule : Et_t [s] strictly increasing, Et_v [V/m] the field at those times.
bool independent_nodes
How the reaction sub-step is partitioned.
std::vector< double > Et_v
double atol_Y
Absolute tolerance on each Y_k [-].
double dt
Output cadence / initial step [s].
std::size_t record_every
Record a frame every N output steps [-].
Solver configuration. All fields have defaults matching a typical H2 arc.
std::vector< double > init_r
Optional seed profile for the initial guess.
int loglevel
Sim1D solver verbosity [-] (0 = silent, 1 = progress, higher = more detail).
double refine_ratio
Refiner: max allowed ratio of adjacent cell widths [-].
double refine_curve
Refiner: max allowed fractional curvature of T per cell [-].
double rho_cp
Volumetric heat capacity [J/m^3/K] — scales the pseudo-transient term used as a Newton fallback.
double refine_slope
Refiner: max allowed fractional change in T slope between adjacent cells [-].
double T_center_guess
Parabolic-seed centerline temperature [K], used to build the default initial guess.
double refine_prune
Refiner: remove grid points whose contribution falls below this threshold [-].
std::vector< double > init_T
Optional seed temperatures [K] at each init_r location.
std::size_t n_points
Initial uniform grid point count [-] (refined during solve if refine_grid).
bool refine_grid
Enable Cantera adaptive grid refinement [-] (true/false) during the solve.
Output of a completed column solve.
double electric_field
Axial E field [V/m] used for this solve (echo of the input).
std::size_t n_points
Number of grid points after adaptive refinement.
std::vector< double > T
Converged temperature [K] at each grid point.
double current
Total arc current [A], trapezoidal quadrature.
std::vector< double > sigma
Electrical conductivity [S/m] evaluated at T.
std::vector< double > kappa
Thermal conductivity [W/m/K] evaluated at T.
std::vector< double > r
Radial grid [m] after refinement, r[0]=0, r[N-1]=R.
Geometry, boundary treatment and stepping controls for one call.
const ThermoAccessor * thermo
Injected equation of state; must outlive the call.
std::vector< double > r_node
Node radii [m], length N, ascending.
int electron_index
Index of the electron species in Y, or -1 for none.
std::vector< double > r_face
Face radii [m], length N+1, ascending.
double t0
Time at the start of the call [s], passed to the thermo accessor and the ghost-state callback.
double cfl
Courant number applied to the CFL bound [-], in (0, 1].
double dt_total
Total interval this call covers [s] – one Strang half-step.
double p_amb
Ambient pressure [Pa] and temperature [K].
GhostStateFn ghost_state
Optional test-only ghost fill; overrides outer_bc_mode when set.
std::vector< double > V_cell
Cell "volumes", cross-sectional area per unit axial length [m^2], length N.
std::vector< double > Y_amb
Ambient composition [-], length K.
std::size_t n_subcycles
Fixed number of equal sub-cycles of dt_total / n_subcycles; 0 means CFL-limited instead.
bool cylindrical_source
Apply the cylindrical geometric source p_i*dr_i/V_i of equation (13).
Diagnostics from one call, enough to assert NUMERICS.md section "Positivity under a strong rarefactio...
std::size_t n_subcycles_used
M actually taken [-].
double rho_min
Min rho over every node and sub-cycle [kg/m^3].
double boundary_mass_out
Mass that left the domain across both boundary faces over the whole call, per unit axial length and p...
double boundary_total_energy_out
TOTAL energy that left the domain across both boundary faces over the whole call, per unit axial leng...
std::vector< double > boundary_species_mass_out
The same, per species [kg/m/rad], length K.
double boundary_electron_energy_out
Electron-pool INTERNAL energy that left the domain across both boundary faces over the whole call,...
double dt_cfl
CFL bound at the initial state [s].
double p_min
Min total p over every node and sub-cycle [Pa].
double pressure_work_electron
Compression work done BY the electron pool over the whole call, per unit axial length and per radian ...
Per-node primitive state, advanced in place.
std::vector< double > rho
Mass density [kg/m^3], length N.
std::vector< double > u_r
Radial velocity [m/s], length N.
std::size_t n_species
K [-].
std::vector< double > Te
Electron temperature [K], length N.
std::vector< double > Y
Mass fractions [-], row-major [N, K].
std::vector< double > Tg
Heavy-pool temperature [K], length N.
Primitive state at one radial node, as the ghost-state callback sees it and returns it.
double Te
Electron temperature [K].
std::vector< double > Y
Species mass fractions [-], length K.
double Tg
Heavy-pool temperature [K].
double u_r
Radial velocity [m/s].
double rho
Mass density [kg/m^3].
double nu_eH
Electron-electron collision frequency [1/s] (Mitchner II-8.11e).
double P_elastic
< Elastic electron-heavy exchange power [W/m^3]
double P_chemical_e
Joule heating power [W/m^3].
double nu_eH_mass_weighted
double nu_ee
Electron-ion momentum-transfer collision frequency [1/s].
double P_Joule
Electron-heavy momentum-transfer collision frequency [1/s] (Mitchner II-13.3).
double P_inelastic
Heavy-species chemical power [W/m^3].
double nu_eI
sum_h nu_eh/m_h [1/kg/s] (Mitchner VIII-3.8, term 2)
double P_chemical_H
Electron chemical power [W/m^3].
Everything PlasmaChannel1D needs to build itself.
std::vector< double > initTe_v
std::vector< double > kappa_e_T
Electron thermal conductivity table kappa_e(Te) [K], [W/m/K].
std::vector< double > initY_v
std::string mech
Cantera mechanism (YAML) file path.
double T_amb
Wall/ambient temperature T_amb [K], and the outer-wall Dirichlet ghost value for Tg.
std::vector< double > initTg_r
Tg(r) at t=0: _r is the radius axis [m], _v the temperature at that radius [K].
std::vector< double > kappa_e_v
std::vector< double > kappa_H_v
std::vector< double > Y_amb
Ambient mass fractions [-], length K, summing to 1: the composition of the gas OUTSIDE the domain.
std::size_t n_points
Number of radial grid points [-]. Ignored when grid is given.
std::vector< double > kappa_H_T
Heavy-pool thermal conductivity table kappa_H(Tg) [K], [W/m/K].
std::vector< double > initTg_v
double R_max
Outer (wall) radius R [m]. Ignored when grid is given.
double D_species
Constant scalar species mass diffusivity D [m^2/s].
bool reacting
Include finite-rate chemistry source terms [-].
std::vector< double > initTe_r
Te(r) at t=0 [m], [K]. Python: Te0 (uniform) or Te_profile.
std::vector< double > initY_r
Y(r) at t=0.
double rho
Mass density rho [kg/m^3] held at every node, unless init_rho_r/init_rho_v give a radial profile.
std::vector< double > grid
Explicit radial grid [m], increasing from 0.
std::string phase
Phase name within mech; must be a PlasmaPhase carrying "e-".
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.
double gap
Inter-electrode gap [m], for the polytropic volume equation.
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.
Everything the diffusion block needs that the grid and the state do not carry: the transport coeffici...
std::size_t electron_index
Index of the electron species [-].
const PropertyTable * kappa_e
Electron conductivity [W/m/K]; an empty table switches electron conduction off.
const std::vector< double > * Y_amb
Ambient mass fractions [-], length K: the species ghost.
double ambient_temperature
Wall/ambient temperature [K], the Dirichlet ghost both conduction calls sit against.
double species_diffusivity
Scalar species mass diffusivity [m^2/s]; zero switches the species block, and with it both energy te...
std::size_t n_species
Number of species K [-].
const PropertyTable * kappa
Heavy-pool conductivity [W/m/K]; an empty table switches heavy conduction off.
const std::vector< double > * species_cp
Per-species [J/kg/K], length K.
const std::vector< double > * molecular_weights
Per-species molecular weight [kg/kmol], length K.
double wall_ghost_density
Mass density of the wall ghost half-cell [kg/m^3], which closes the face coefficient of the outermost...