rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
Loading...
Searching...
No Matches
bindings.cpp
Go to the documentation of this file.
1// pybind11 bindings for the rizer Cantera 1D plasma extension (_plasma1d).
2//
3// This extension is a pure C++ implementation of the Elenbaas-Heller column
4// problem, using Cantera's 1D simulation framework (Sim1D) to solve the
5// steady-state radial energy balance with a user-specified axial electric field.
6// This file contains the pybind11 bindings, which are a thin wrapper around the
7// C++ implementation in PlasmaColumnSolver.h/cpp.
8//
9// Design rule: "flat numerics only" across the Python boundary.
10// - No Cantera C++ type (Solution, Domain1D, Sim1D, ...) is ever exposed to
11// Python. This sidesteps all ABI-sharing concerns with the stock `cantera`
12// Python package; both can be loaded in the same process without conflicts.
13// - Properties enter as parallel (T, value) numpy arrays; they are converted
14// to PropertyTable objects inside the binding lambda.
15// - The converged profile leaves as a plain Python dict of numpy arrays and
16// Python scalars — no custom C++ types cross the boundary.
17//
18// Exposed API:
19// cantera_version() -> str version of the linked libcantera
20// solve_column(...) -> dict solve and return (r, T, sigma, kappa,
21// current, electric_field, n_points)
22
23#ifndef NOMINMAX
24#define NOMINMAX
25#endif
26#include <pybind11/pybind11.h>
27#include <pybind11/stl.h>
28#include <pybind11/numpy.h>
29
30#include "cantera/base/global.h"
31
32#include "PropertyTable.h"
33#include "PlasmaColumnSolver.h"
34
35#include <vector>
36
37namespace py = pybind11;
38
39namespace {
40
41// Build a PropertyTable from two equal-length numpy arrays (T strictly increasing).
42// Transform numpy arrays into std::vector<double> for the PropertyTable constructor.
43rizer::PropertyTable makeTable(const py::array_t<double>& T,
44 const py::array_t<double>& values)
45{
46 // Create unchecked accessors to the input arrays, which avoids bounds checking on every access.
47 auto t = T.unchecked<1>();
48 auto v = values.unchecked<1>();
49 // Copy the data into std::vector<double> for the PropertyTable constructor.
50 std::vector<double> tv(static_cast<std::size_t>(t.shape(0)));
51 std::vector<double> vv(static_cast<std::size_t>(v.shape(0)));
52 // Copy the data from the numpy arrays into the std::vectors.
53 for (py::ssize_t i = 0; i < t.shape(0); i++) {
54 tv[static_cast<std::size_t>(i)] = t(i);
55 }
56 for (py::ssize_t i = 0; i < v.shape(0); i++) {
57 vv[static_cast<std::size_t>(i)] = v(i);
58 }
59 // Construct and return the PropertyTable from the vectors.
60 return rizer::PropertyTable(std::move(tv), std::move(vv));
61}
62
63} // namespace
64
65PYBIND11_MODULE(_plasma1d, m)
66{
67 m.doc() = "rizer Cantera 1D plasma extension (ThermalPlasmaColumn1D)";
68
69 m.def("cantera_version", []() { return Cantera::version(); },
70 "Version string of the linked libcantera.");
71
72 // Define the solve_column() binding.
73 // The lambda converts the Python inputs into C++ types, calls solveColumn(),
74 // and converts the result back into a Python dict of numpy arrays and scalars.
75 m.def("solve_column",
76 [](double R, // Outer wall radius [m].
77 double electric_field, // Axial E field [V/m].
78 double T_wall, // Wall temperature [K].
79 py::array_t<double> sigma_T, // Temperature values [K] — 1-D array, strictly increasing.
80 py::array_t<double> sigma_v, // Corresponding sigma values [S/m] — 1-D array.
81 py::array_t<double> kappa_T, // Temperature values [K] — 1-D array, strictly increasing.
82 py::array_t<double> kappa_v, // Corresponding kappa values [W/(m·K)] — 1-D array.
83 py::object prad_T, // Temperature values [K] — 1-D array, strictly increasing, or None.
84 py::object prad_v, // Corresponding P_rad values [W/m³] — 1-D array, or None.
85 std::size_t n_points, // Initial uniform grid count.
86 double T_center_guess, // Parabolic-seed centerline temperature [K].
87 double rho_cp, // rho*cp [J/m³/K] for pseudo-transient scaling.
88 int loglevel, // Sim1D verbosity (0 = silent).
89 bool refine_grid, // Enable Cantera adaptive grid refinement.
90 double refine_ratio, // Refiner: max ratio of adjacent cell widths .
91 double refine_slope, // Refiner: max fractional slope change per cell.
92 double refine_curve, // Refiner: max fractional curvature per cell.
93 double refine_prune, // Refiner: remove points below this threshold.
94 py::object init_r, // Optional seed radii [m], strictly increasing, 0..R, or None.
95 py::object init_T // Optional seed temperatures [K] at init_r, or None.
96 )
97 {
98 // Convert the input numpy arrays into PropertyTable objects.
99 rizer::PropertyTable sigma = makeTable(sigma_T, sigma_v);
100 rizer::PropertyTable kappa = makeTable(kappa_T, kappa_v);
101 // For the optional radiation property, check if the inputs are None.
102 // If either is None, create a constant PropertyTable with value 0.0.
103 // Otherwise, create a PropertyTable from the provided arrays.
105 (prad_T.is_none() || prad_v.is_none())
107 : makeTable(prad_T.cast<py::array_t<double>>(),
108 prad_v.cast<py::array_t<double>>());
109 // Set up the solver options from the provided parameters.
111 opts.n_points = n_points;
112 opts.T_center_guess = T_center_guess;
113 opts.rho_cp = rho_cp;
114 opts.loglevel = loglevel;
115 opts.refine_grid = refine_grid;
116 opts.refine_ratio = refine_ratio;
117 opts.refine_slope = refine_slope;
118 opts.refine_curve = refine_curve;
119 opts.refine_prune = refine_prune;
120
121 // If the user provided an initial seed profile,
122 // convert the numpy arrays into std::vectors.
123 if (!init_r.is_none() && !init_T.is_none()) {
124 auto ir = init_r.cast<py::array_t<double>>();
125 auto it = init_T.cast<py::array_t<double>>();
126 auto ira = ir.unchecked<1>();
127 auto ita = it.unchecked<1>();
128 opts.init_r.resize(static_cast<std::size_t>(ira.shape(0)));
129 opts.init_T.resize(static_cast<std::size_t>(ita.shape(0)));
130 for (py::ssize_t i = 0; i < ira.shape(0); i++) {
131 opts.init_r[static_cast<std::size_t>(i)] = ira(i);
132 }
133 for (py::ssize_t i = 0; i < ita.shape(0); i++) {
134 opts.init_T[static_cast<std::size_t>(i)] = ita(i);
135 }
136 }
137
138 // Call the C++ solver function, releasing the GIL since it is pure C++.
139 // The result is a ColumnResult struct containing the solution.
141 {
142 py::gil_scoped_release release; // solve is pure C++
143 res = rizer::solveColumn(R, electric_field, T_wall,
144 sigma, kappa, prad, opts);
145 }
146
147 // Convert the result into a Python dict of numpy arrays and scalars.
148 py::dict out;
149 out["r"] = py::array_t<double>(res.r.size(), res.r.data());
150 out["T"] = py::array_t<double>(res.T.size(), res.T.data());
151 out["sigma"] = py::array_t<double>(res.sigma.size(), res.sigma.data());
152 out["kappa"] = py::array_t<double>(res.kappa.size(), res.kappa.data());
153 out["current"] = res.current;
154 out["electric_field"] = res.electric_field;
155 out["n_points"] = res.n_points;
156 return out;
157 },
158 py::arg("R"), py::arg("electric_field"), py::arg("T_wall"),
159 py::arg("sigma_T"), py::arg("sigma_v"),
160 py::arg("kappa_T"), py::arg("kappa_v"),
161 py::arg("prad_T") = py::none(), py::arg("prad_v") = py::none(),
162 py::arg("n_points") = 41, py::arg("T_center_guess") = 10000.0,
163 py::arg("rho_cp") = 1.0e3, py::arg("loglevel") = 0,
164 py::arg("refine_grid") = true, py::arg("refine_ratio") = 4.0,
165 py::arg("refine_slope") = 0.05, py::arg("refine_curve") = 0.10,
166 py::arg("refine_prune") = 0.0,
167 py::arg("init_r") = py::none(), py::arg("init_T") = py::none(),
168 "Solve the steady radial Elenbaas-Heller column.\n\n"
169 "Assembles [Empty1D | ThermalPlasmaColumn1D | Empty1D] in a Cantera\n"
170 "Sim1D, solves with Newton + pseudo-transient fallback, and optionally\n"
171 "refines the radial grid adaptively.\n\n"
172 "Parameters\n"
173 "----------\n"
174 "R : outer wall radius [m]\n"
175 "electric_field: axial E field [V/m]\n"
176 "T_wall : wall temperature [K]\n"
177 "sigma_T/v : tabulated sigma(T) [S/m] — 1-D arrays of equal length\n"
178 "kappa_T/v : tabulated kappa(T) [W/m/K]\n"
179 "prad_T/v : tabulated P_rad(T) [W/m^3], or None for no radiation\n"
180 "n_points : initial uniform grid count\n"
181 "T_center_guess: parabolic-seed centerline temperature [K]\n"
182 "rho_cp : rho*cp [J/m^3/K] for pseudo-transient scaling\n"
183 "loglevel : Sim1D verbosity (0 = silent)\n"
184 "refine_grid : enable adaptive grid refinement\n"
185 "refine_ratio/slope/curve/prune: Refiner parameters\n"
186 "init_r/T : optional seed profile T(r); overrides the parabola\n\n"
187 "Returns\n"
188 "-------\n"
189 "dict with keys r, T, sigma, kappa (numpy arrays) and\n"
190 "current [A], electric_field [V/m], n_points (scalars).");
191}
PYBIND11_MODULE(_plasma1d, m)
Definition bindings.cpp:65
static PropertyTable constant(double value)
A two-point constant table returning value for any T.
string version()
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.
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 verbosity (0 = silent, 1 = progress)
double refine_ratio
Refiner: max ratio of adjacent cell widths.
double refine_curve
Refiner: max fractional curvature per cell.
double rho_cp
rho*cp [J/m³/K] — scales the pseudo-transient term
double refine_slope
Refiner: max fractional slope change per cell.
double T_center_guess
parabolic-seed centerline temperature [K]
double refine_prune
Refiner: remove points below this threshold.
std::vector< double > init_T
seed temperatures [K] at init_r
std::size_t n_points
initial uniform grid count (refined during solve)
bool refine_grid
enable Cantera adaptive grid refinement
Output of a completed column solve.
double electric_field
E [V/m] used for this solve.
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
I = 2π·E·∫sigma(T)·r dr [A], trapezoidal.
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