|
rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
|
#include <PlasmaChannelSolver.h>
Public Attributes | |
| PlasmaChannelConfig | cfg |
| Domain physics + initial/boundary state. | |
| double | dt = 1.0e-9 |
| Output cadence / initial step [s]. | |
| std::size_t | n_steps = 1000 |
| Number of output steps [-] (dt*n_steps = total time [s]). | |
| std::size_t | record_every = 1 |
| Record a frame every N output steps [-]. | |
| int | loglevel = 0 |
| Diagnostic verbosity [-] (0 = silent; >0 prints progress). | |
| double | rtol = 1.0e-7 |
| Error tolerances for the stiff BDF backend, which sub-steps internally until it meets them, so its accuracy does not depend on the output estimate. | |
| double | atol_T = 1.0e-3 |
| Absolute tolerance on Tg and Te [K]. | |
| double | atol_Y = 1.0e-9 |
| Absolute tolerance on each Y_k [-]. | |
| bool | independent_nodes = false |
| How the reaction sub-step is partitioned. | |
| std::size_t | n_threads = 1 |
| Threads advancing the per-node systems of independent_nodes. | |
| std::vector< double > | Et_t |
| Field-vs-time schedule \(E(t)\): Et_t [s] strictly increasing, Et_v [V/m] the field at those times. | |
| std::vector< double > | Et_v |
| double | gap = 0.0 |
| Inter-electrode gap [m]. | |
| std::function< double(double, double)> | plasma_voltage |
| Closes the electrical circuit on the channel's own resistance, given \((t\,[\mathrm{s}], R_p\,[\Omega])\) and returning the plasma terminal voltage \(V_p\) [V]. | |
Definition at line 27 of file PlasmaChannelSolver.h.
| double rizer::ChannelOptions::atol_T = 1.0e-3 |
Absolute tolerance on Tg and Te [K].
Definition at line 61 of file PlasmaChannelSolver.h.
| double rizer::ChannelOptions::atol_Y = 1.0e-9 |
Absolute tolerance on each Y_k [-].
Definition at line 62 of file PlasmaChannelSolver.h.
| PlasmaChannelConfig rizer::ChannelOptions::cfg |
Domain physics + initial/boundary state.
Definition at line 29 of file PlasmaChannelSolver.h.
| double rizer::ChannelOptions::dt = 1.0e-9 |
Output cadence / initial step [s].
Definition at line 30 of file PlasmaChannelSolver.h.
| std::vector<double> rizer::ChannelOptions::Et_t |
Field-vs-time schedule \(E(t)\): Et_t [s] strictly increasing, Et_v [V/m] the field at those times.
Both empty leaves cfg.electric_field as a constant; otherwise the field is interpolated and pushed into the domain at every internal step. The schedule must cover [0, n_steps * dt + dt]: CVODE's step is capped at dt, so that is the farthest it evaluates the field. A shorter one is refused.
Here rather than on PlasmaChannelConfig because only a driver that steps time can evaluate a schedule: the domain itself holds one field value and is told when to change it.
Definition at line 126 of file PlasmaChannelSolver.h.
| std::vector<double> rizer::ChannelOptions::Et_v |
Definition at line 126 of file PlasmaChannelSolver.h.
| double rizer::ChannelOptions::gap = 0.0 |
Inter-electrode gap [m].
Required by plasma_voltage and ignored without it: it converts the channel's conductance into a resistance ( \(R_p = \mathrm{gap} / \int\sigma\,\mathrm{d}A\)) and the returned terminal voltage back into a field ( \(E = V_p/\mathrm{gap}\)).
Definition at line 132 of file PlasmaChannelSolver.h.
| bool rizer::ChannelOptions::independent_nodes = false |
How the reaction sub-step is partitioned.
Why it is worth a choice. The coupled system must take the step the stiffest node demands, at every node. Measured on the reference CH4 discharge, one cell alone needs as many internal steps as the whole coupled domain (1392 against 1346), while the outermost cell needs 135 – so the quiescent nodes are advanced ten times more finely than they need. Splitting them measures 2.19x at 15 nodes, 3.41x at 41 and 4.57x at 81, and 4.67x / 8.38x / 12.31x with n_threads at 4. Both factors grow with resolution, because every node added to the quiet outer region costs the coupled solver the stiffest node's step.
The gain is entirely in how unlike the nodes are, not in the node count. On the same channel relaxing with the field off, where every node behaves alike, the per-node form takes 16x MORE internal steps than the coupled one (3955 against 248 at 15 nodes) and is the slower of the two: nothing was dragging the coupled step, and each node now pays its own startup. Use it for a discharge with a hot core and a cold edge, not for a uniform relaxation.
The two do not agree bit for bit and are not meant to: per-node error control picks different step sequences. On a smooth case they agree to the absolute-tolerance floor and converge as it tightens – 1.9e-6, 5.8e-8 then 1.4e-8 of each other as (atol_T, atol_Y) go from (1e-3, 1e-9) to (1e-7, 1e-13). Across a breakdown knee no pointwise comparison means much in either direction: n_e climbs five decades there, and the coupled form differs from ITSELF by 6.5e-4 between rtol 1e-9 and 1e-10. The coupled form stays the default so that every recorded result in this repository keeps reproducing.
Definition at line 99 of file PlasmaChannelSolver.h.
| int rizer::ChannelOptions::loglevel = 0 |
Diagnostic verbosity [-] (0 = silent; >0 prints progress).
Definition at line 34 of file PlasmaChannelSolver.h.
| std::size_t rizer::ChannelOptions::n_steps = 1000 |
Number of output steps [-] (dt*n_steps = total time [s]).
Definition at line 31 of file PlasmaChannelSolver.h.
| std::size_t rizer::ChannelOptions::n_threads = 1 |
Threads advancing the per-node systems of independent_nodes.
1 (the default) keeps the loop serial; the nodes are genuinely independent there, so nothing but the evaluator pool stands between serial and parallel.
Not free to raise: each thread needs its own PlasmaCell over its own Solution, and a Solution is a mechanism parse (227 ms for the 31-species CH4 mechanism), paid once before the first step. Asking for more threads than nodes is pointless rather than wrong, and is capped to the node count.
Rejected, not ignored, without independent_nodes: one banded system has no per-node loop to spread.
Definition at line 114 of file PlasmaChannelSolver.h.
| std::function<double(double, double)> rizer::ChannelOptions::plasma_voltage |
Closes the electrical circuit on the channel's own resistance, given \((t\,[\mathrm{s}], R_p\,[\Omega])\) and returning the plasma terminal voltage \(V_p\) [V].
Unset (the default) leaves PlasmaChannelConfig::electric_field, or the Et_t/Et_v schedule above, as the prescribed field – one the plasma cannot push back on. Set, it is the only source of the field: a non-zero electric_field or a schedule alongside it is refused.
The circuits stay where they are. rizer/electrical_model/circuit/ holds a dozen classes under active development, several of them stateful, so this is a seam rather than a port: two plain numbers cross per evaluation, which keeps the numpy-only boundary rule of .claude/rules/cantera-extension.md intact.
Explicit coupling, deliberately. \(R_p\) is frozen over each global step and the field varies within it by a two-point interpolation of \(V_p\), mirroring ChannelReactor1D.solve_split_circuit. Making it implicit would put this callback inside CVODE's residual, where it is hit once per Jacobian column instead of twice per step. Measured cost as used here: a Python transmission-line circuit answers in 1.56 us, so two calls per step over 200 steps is 0.62 ms.
Called from inside the solve, which releases the GIL, so a Python implementation must re-acquire it (the binding's wrapper does).
Definition at line 159 of file PlasmaChannelSolver.h.
| std::size_t rizer::ChannelOptions::record_every = 1 |
Record a frame every N output steps [-].
Definition at line 33 of file PlasmaChannelSolver.h.
| double rizer::ChannelOptions::rtol = 1.0e-7 |
Error tolerances for the stiff BDF backend, which sub-steps internally until it meets them, so its accuracy does not depend on the output estimate.
Loosening them buys much less than the step count suggests. On the reference CH4 discharge, rtol 1e-7 -> 1e-4 cuts the internal steps from 1346 to 784 and the wall clock by 1.04x; the loosest setting measured, (1e-4, 1e-1, 1e-6), reaches 2.50x fewer steps for 1.30x wall clock and 62% error. Bigger steps are harder steps – more Jacobian rebuilds and right-hand side calls per step – so the per-step cost rises as the count falls, and the two nearly cancel. The sequence is not even monotone: rtol 1e-6 runs 0.90x the default's speed at 12% fewer steps. The defaults below are the measured sweet spot, not a guess.
atol_T is a temperature floor [K] and atol_Y a mass-fraction floor [-], applied per component. atol_Y has to sit ABOVE the truly-trace species level: a species at \(Y \sim 10^{-12}\) oscillating about zero cannot be tracked to \(10^{-15}\), and CVODE then drives its step chasing meaningless trace-species error (seen as a stall at the channel edge, where chemistry is stiff but species are vanishing). rtol governs the O(1) carriers and the temperatures; this is a transport+chemistry PDE solve, not a 0D point, so it does not need the 0D reactor's rtol 1e-10. Relative tolerance, all components [-]
Definition at line 60 of file PlasmaChannelSolver.h.