rizer.models.nrp.plasma_channel_2T1D#
Transient 1D two-temperature reacting plasma channel (Cantera-based).
PlasmaChannel is the 1-D radial, transient counterpart of rizer’s 0-D
rizer.models.nrp.isomass_2T_volume_reactor.Isomass2TVolumeReactor: it
solves finite-rate two-temperature plasma chemistry together with radial heat
conduction, and exposes the discharge-channel diameter expansion D(t) for
comparison with rizer’s engineering prediction
(rizer.models.nrp.engineering_model.diffusion) and experiment.
The heavy solve lives in the compiled rizer.cantera_ext._plasma1d; this
wrapper builds the initial state, calls it, and post-processes the radial
temperature history into a channel radius.
Notes
The native-rate air_plasma_Laux2000 mechanism loads in C++; the Goutier CH4
mechanism has Python-only ExtensibleRate reactions and needs C++ ports for a
fully-kinetic CH4 run (see ARCHITECTURE.md). For a thermal (LTE-conduction)
CH4 expansion, run with reacting=False and an injected CH4 kappa_H(T).
Classes#
Transient radial two-temperature reacting plasma channel. |
Module Contents#
- class rizer.models.nrp.plasma_channel_2T1D.PlasmaChannel(mech: str, phase: str, R_max: float, rho: float, Y0: numpy.ndarray | list[float], Y_amb: numpy.ndarray | list[float], *, grid: numpy.ndarray | list[float] | None = None, Tg_profile: tuple[numpy.ndarray, numpy.ndarray] | None = None, Te_profile: tuple[numpy.ndarray, numpy.ndarray] | None = None, Y0_profile: tuple[numpy.ndarray, numpy.ndarray] | None = None, T_amb: float = 300.0, Tg0: float | None = None, Te0: float | None = None, kappa_H: tuple[numpy.ndarray, numpy.ndarray] | float | None = None, kappa_e: tuple[numpy.ndarray, numpy.ndarray] | float | None = None, D_species: float = 0.0, hydrodynamics: bool = False, rho_profile: tuple[numpy.ndarray, numpy.ndarray] | None = None, u_r_profile: tuple[numpy.ndarray, numpy.ndarray] | None = None, outer_bc_mode: str = 'ambient_ghost', p_amb: float = -1.0, cfl: float = 0.9, sigma: tuple[numpy.ndarray, numpy.ndarray] | float = 0.0, nu_E: float = 0.0, nu_m: float | tuple[numpy.ndarray, numpy.ndarray] = 0.0, mtcf: list[rizer.transport.collision_frequency.MomentumTransferCollisionFrequencyModel] | None = None, Te_min: float = 200.0, Te_max: float = 100000.0, Te_n: int = 400, electric_field: float | tuple[numpy.ndarray, numpy.ndarray] = 0.0, reacting: bool = True, dt: float = 1e-08, n_steps: int = 100, record_every: int = 1, n_points: int = 81, loglevel: int = 0, gap: float | None = None, rtol: float = 1e-07, atol_T: float = 0.001, atol_Y: float = 1e-09, independent_nodes: bool = False, n_threads: int = 1, plasma_voltage: collections.abc.Callable[[float, float], float] | None = None)#
Transient radial two-temperature reacting plasma channel.
Assumptions and domain of validity
This model is a 1-D radial, axisymmetric generalisation of rizer’s 0-D
Isomass2TVolumeReactor. Read these before interpreting results:Density and flow depend on
hydrodynamics. Off (the default), there is no continuity or momentum equation:rhois a fixed parameter, the state per node is(Tg, Te, Y_s)and there is no flow. Pressure is then not held constant: a hot core at fixedrhosits at an elevated local pressure, and the recordedn_eis the model’s own value at that(T, rho)state. That mode is valid when the channel evolves by conduction/diffusion and chemistry, and not valid for pressure-driven (shock / Braginskii) gas-dynamic expansion.On, the state gains
(rho, u_r)and a radial hydrodynamic sub-step advances them in a Strang split around the reaction step, which is what represents that expansion.rhothen sets the initial density andrho_profilea radial one;p_ambmust be given, since it has no default that is safe to assume.Prescribed electric field
E(t); no self-consistent field. Poisson is not solved and the plasma is treated quasi-neutral, so the dielectric-relaxation timescale never enters. Valid for a given/measured applied field; not valid for self-consistent sheath/streamer dynamics (use a drift-diffusion-Poisson solver for those).Two-temperature (
Te != Tg): separate electron and heavy-species energy equations coupled by elastic/inelastic exchange, assuming a Maxwellian EEDF atTe.Heat conduction: two-temperature Fourier — radial conduction for both
Tg(kappa_H(Tg)) andTe(kappa_e(Te)), each active only if its conductivity table is supplied.Species transport: Fickian with a single constant scalar
D_species. No thermal diffusion (Soret) or Dufour, not mixture-averaged / multicomponent (oneDfor every species), and no ambipolar diffusion for charged species (electrons and ions shareD, no field coupling). Species diffusion is frozen whenD_species == 0(the default). Species diffusion also feeds back into both energy equations: a compressibility term (from the two-temperature ideal-gas EOS) and an enthalpy-diffusion (species heat transport) term, each nonzero whenever a composition gradient coincides with species of different heat capacity – not just the mass-fraction equations (seeARCHITECTURE.md).Joule heating
sigma*E^2. Two mutually exclusive closures forsigma(and, for the electron energy equation, the elastic electron-heavy exchange frequency): (a)mtcf, a composition-resolved collision model evaluated live from the evolving composition every step (the same model the 0-DIsomass2TVolumeReactoruses) – preferred, sincesigmathen tracks the reacting mixture instead of a frozen snapshot; or (b) the legacy closure:sigmafrom asigma(Te)table, or – ifnu_m > 0–sigma = n_e e^2/(m_e nu_m)from the localn_e, plus a separately-suppliednu_E. Passing both raisesValueError.Geometry/BC: cylindrical 1-D radial, axisymmetric;
r=0symmetry;r=R_maxDirichlet wall atT_amb(and ambient compositionY_ambfor diffusion).Numerics: a stiff adaptive-order BDF integrator (Cantera CVODE over the method-of-lines RHS), error-controlled in both order and step, which is what handles the field-driven plasma chemistry. The stiff limit is the electron-energy equation (tiny electron heat capacity -> sub-ns
Tedynamics): start gently (Te=Tg) and use a modest field, as rizer’s 0D NRP reactor does. Both air and the CH4Goutier2025mechanism run this way.
- Parameters:
mech (
str) – Cantera mechanism file and phase name (aPlasmaPhasewithe-).phase (
str) – Cantera mechanism file and phase name (aPlasmaPhasewithe-).R_max (
float) – Outer radius of the computational domain [m] (ignored ifgridis given).rho (
float) – (Fixed) mass density [kg/m^3].Y0 (
array_like) – Initial composition inside the domain where noY0_profileis supplied (length = number of species). The electron mass fraction must be positive at every node, here and inY0_profile: the electron temperature is undefined where there are no electrons, so an electron-free start raises. Seed a small fraction with a matching positive ion.Y_ambmay hold no electrons.Y_amb (
array_like) –Ambient mass fractions OUTSIDE the domain (length = number of species). Used for the outer-wall ghost of the diffusion terms, the wall-ghost density, and – with
hydrodynamics– the composition of gas flowing inward across the wall face.Required, and never derived from
Y0. The two are independent physical quantities: a seededY0left to stand in for the ambient is a plasma flowing inward at the boundary, which on the reference CH4 discharge puts the wall node atn_e2.3e20 instead of 2.9e14 and moves the plasma resistance by up to a factor of two, since the outermost cell carries the largest area weight inintegral(sigma dA). PassY_amb=Y0only when the boundary genuinely holds the initial composition.grid (
array_likeorNone) – Explicit, increasing radial grid [m] (grid[0] == 0). Overridesn_points/R_max; installed directly (Cantera-flame style). If None, a uniformn_pointsgrid on[0, R_max]is built.Tg_profile (
tuple(array,array)orNone) –(r, T)initial radial temperature profiles [m, K]. If None, uniformT_amb.Te_profile (
tuple(array,array)orNone) –(r, T)initial radial temperature profiles [m, K]. If None, uniformT_amb.Y0_profile (
tuple(array,2d-array)orNone) –(r, Y)initial radial composition,Yshaped(len(r), nsp). If None, the uniformY0is used everywhere.T_amb (
float) – Wall/ambient temperature [K]: the outer-wall Dirichlet ghost value for bothTgandTe, and the default forTg0/Te0.Initial gas and electron temperature [K], uniform where no
Tg_profile/Te_profileis supplied. None (the default) starts them atT_amb.They are the initial state, not the boundary. A channel that starts hot behind a cold wall sets these above
T_amb; one that starts at ambient leaves them None. GivingT_ambboth jobs is what makes the two easy to confuse, which is why they are separate arguments here. In C++ they are separate arguments ofsolve_1D_unsteady_dischargerather than fields ofPlasmaChannelConfig: the binding turns each into a flat two-pointinitTg_r/initTg_v(orinitTe_*) profile, and onlyT_ambreaches the config as a temperature of its own.Initial gas and electron temperature [K], uniform where no
Tg_profile/Te_profileis supplied. None (the default) starts them atT_amb.They are the initial state, not the boundary. A channel that starts hot behind a cold wall sets these above
T_amb; one that starts at ambient leaves them None. GivingT_ambboth jobs is what makes the two easy to confuse, which is why they are separate arguments here. In C++ they are separate arguments ofsolve_1D_unsteady_dischargerather than fields ofPlasmaChannelConfig: the binding turns each into a flat two-pointinitTg_r/initTg_v(orinitTe_*) profile, and onlyT_ambreaches the config as a temperature of its own.kappa_H (
tuple(array,array)orfloatorNone) – Heavy-pool / electron thermal conductivity, as(T, value)tables or a constant [W/m/K]. None disables that conduction.kappa_e (
tuple(array,array)orfloatorNone) – Heavy-pool / electron thermal conductivity, as(T, value)tables or a constant [W/m/K]. None disables that conduction.D_species (
float) – Constant species mass diffusivity [m^2/s] (0 = frozen transport).sigma (
tuple(array,array)orfloat) – Electrical conductivitysigma(Te)[S/m] for Joule heating (legacy closure; mutually exclusive withmtcf).nu_E (
float) – Effective electron-heavy elastic energy-exchange frequency [1/s] (legacy closure; mutually exclusive withmtcf).nu_m (
floatortuple(array,array)) – Electron momentum-transfer frequency [1/s]; if > 0,sigma = n_e e^2/(m_e nu_m)from the localn_e(overrides thesigmatable). Legacy closure; mutually exclusive withmtcf.mtcf (
listofMomentumTransferCollisionFrequencyModelorNone) – Per-species momentum-transfer collision-frequency models (as built byrizer.transport.loaders.get_momentum_transfer_collision_frequencies_list()), one per species in mechanism order. When given,sigmaand the electron-heavy elastic exchange are evaluated live from the evolving composition every step via the same composition-resolved collision modelIsomass2TVolumeReactoruses, instead of the frozensigma/nu_m/nu_Eclosure below. Mutually exclusive withsigma,nu_m, andnu_E: passing a non-default value of any of those together withmtcfraisesValueError.Te_min (
float,float,int) – Log-spaced electron-temperature grid [K],[K],[-] the collision model (mtcf) precomputes its per-species cross-section tables on. Only used whenmtcfis given.Te_max (
float,float,int) – Log-spaced electron-temperature grid [K],[K],[-] the collision model (mtcf) precomputes its per-species cross-section tables on. Only used whenmtcfis given.Te_n (
float,float,int) – Log-spaced electron-temperature grid [K],[K],[-] the collision model (mtcf) precomputes its per-species cross-section tables on. Only used whenmtcfis given.electric_field (
floatortuple(array,array)) – Constant fieldE[V/m], or an arbitrary schedule(t, E)[s, V/m] (linearly interpolated, e.g. a pulse) supplied from Python.reacting (
bool) – Include finite-rate chemistry.dt (
float,int,int) – Output cadence / initial step [s] (dt*n_steps= total time), number of output steps, recording cadence.n_steps (
float,int,int) – Output cadence / initial step [s] (dt*n_steps= total time), number of output steps, recording cadence.record_every (
float,int,int) – Output cadence / initial step [s] (dt*n_steps= total time), number of output steps, recording cadence.n_points (
int) – Radial grid points (ignored ifgridis given).rtol (
float) – Error tolerances: relative [-], a temperature floor [K], and a mass-fraction floor [-]. Loosening them buys much less than the step count suggests – bigger steps are harder steps, so the per-step cost rises as the count falls and the two nearly cancel.atol_T (
float) – Error tolerances: relative [-], a temperature floor [K], and a mass-fraction floor [-]. Loosening them buys much less than the step count suggests – bigger steps are harder steps, so the per-step cost rises as the count falls and the two nearly cancel.atol_Y (
float) – Error tolerances: relative [-], a temperature floor [K], and a mass-fraction floor [-]. Loosening them buys much less than the step count suggests – bigger steps are harder steps, so the per-step cost rises as the count falls and the two nearly cancel.independent_nodes (
bool) –False (the default) solves the reaction sub-step as one banded system over the whole domain; True gives every node its own integrator and step size. Legal only with every transport term off, and rejected otherwise.
Worth 2.19x / 3.41x / 4.57x at 15 / 41 / 81 nodes on a discharge with a hot core and a cold edge, and SLOWER on a uniform relaxation: the gain is in how unlike the nodes are, not their number.
n_threads (
int) – Threads advancing the per-node systems. Needsindependent_nodes=True; each thread costs one mechanism parse before the first step. Measured 2.57x at 41 nodes on four threads, and bit-identical at every count.plasma_voltage (
typing.CallableorNone) –(t [s], R_p [Ohm]) -> V_p [V], closing an electrical circuit on the channel’s own radially-integrated conductance,R_p = gap / integral(sigma dA). Needsgap. None (the default) leaves the field prescribed byelectric_field, which is a field the plasma cannot push back on.R_pis frozen over each global step andV_pinterpolated across it, so the callable sees two monotonically increasing times per step and never the integrator’s internal trial times – which is what a stateful circuit, such as a transmission line keeping a wave history, requires. AddsRp,Rp_state,Vp,IandEto the recorded history.hydrodynamics (
bool) – Run the radial hydrodynamic sub-step in a Strang split around the reaction step. Off by default, and off meansrhois a fixed parameter and there is no flow. On,p_ambmust be given,record_everymust dividen_steps, and the grid needs at least 2 points.rho_profile (
tuple(array,array)orNone) – Initial radial density(r, rho)[m],[kg/m^3], every value > 0. None holds the scalarrhoat every node.u_r_profile (
tuple(array,array)orNone) – Initial radial velocity(r, u_r)[m],[m/s]. None starts at rest. Only read whenhydrodynamicsis set.outer_bc_mode (
str) –"ambient_ghost"(default, and the accurate choice whileR_maxsits beyond the front) or"pressure_outlet", which pinspat the wall and is measurably reflecting. Only read whenhydrodynamicsis set, but validated either way."pressure_outlet"is a documented non-default, kept as the comparison point that justifies the default rather than as a treatment to reach for: validation case 21 measures it returning 33% to 62% of an incident wave againstambient_ghost’s 1.3% to 2.4%, and it rings instead of settling. Removing it would delete the only evidence in the repo that the default is the right one. No example or driver selects it.p_amb (
float) – Ambient pressure the outer boundary imposes [Pa]. No default that silently validates: a zero is a vacuum, sohydrodynamicsrequires it to be set.cfl (
float) – Courant number for the hydro sub-step’s own CFL sub-cycling [-], in (0, 1].
- T_amb#
- property t: numpy.ndarray#
Recorded times [s].
- property r: numpy.ndarray#
Radial grid [m].
- property species: list[str]#
Species names, in the mechanism order used by
mass_fractions().
- temperature_profiles() tuple[numpy.ndarray, numpy.ndarray]#
Return the recorded gas- and electron-temperature histories.
- Returns:
Tg (
numpy.ndarray) – Gas (heavy-species) temperature [K], shape(nframes, npts).Te (
numpy.ndarray) – Electron temperature [K], shape(nframes, npts).
- electron_density() numpy.ndarray#
Electron number density
n_e(r, t), shape(nframes, npts)[1/m^3].Computed inside the solver as
X_e * P / (k_B * Tmean)per node (the samen_eused in the electron energy balance), so it is the model’s own value rather than a post-hoc reconstruction.
- mass_fractions() numpy.ndarray#
Species mass fractions
Y_s(r, t), shape(nframes, npts, nsp)[-].
- density_profiles() numpy.ndarray#
Mass density
rho(r, t), shape(nframes, npts)[kg/m^3].Constant at the configured
rhounlesshydrodynamicsis on, in which case the hydro sub-step solves it.
- velocity_profiles() numpy.ndarray#
Radial velocity
u_r(r, t), shape(nframes, npts)[m/s].Zero throughout unless
hydrodynamicsis on.
- pressure_profiles() numpy.ndarray#
Total pressure
p(r, t), shape(nframes, npts)[Pa].Derived from the two-temperature equation of state rather than held as a state variable, and computed inside the solver so it comes from the same state-setting the source terms use.
- field() numpy.ndarray#
Return the applied electric field
E(t)[V/m] at the recorded output times.With
plasma_voltageset, this is the circuit’s field as the solver recorded it,V_p / gapat the end of each step (0 att = 0). Otherwise it reconstructs the prescribed field the C++ solver evaluated: a constant, or linear interpolation of the(t, E)schedule supplied at construction (seeChannelOptions::Et_t/Et_vinPlasmaChannelSolver.h).- Returns:
Field [V/m] at each recorded time in
t.- Return type:
- total_current() numpy.ndarray#
Total current
I(t)[A] through the channel cross-section.I = E(t) \int_0^R \sigma(r) 2\pi r\,dr.\sigmafollows the same fallback as the solver’s own Joule-heating term:\sigma = n_e e^2/(m_e \nu_m)(from the localn_e) whennu_m > 0or anu_m(Te)table is given; otherwise thesigma(Te)conductivity table supplied at construction (matchingPlasmaCell::transport, so Joule heating configured purely viasigmais still diagnosable). Assumes the recorded radial profile is uniform along the inter-electrode gap.The integral is the cell sum of
_cell_areas, which is the quadrature the plasma resistance uses, so the current reported here and theR_pa circuit closes on describe the same channel.With
plasma_voltageset, this is the current the circuit drove and the solver recorded,V_p / R_pwithR_pfrozen at the start of each step, for either closure. Rebuilding it from each frame’s own conductivity would divide by the end-of-step resistance instead, which across a breakdown differs from the one that carried the current.- Raises:
NotImplementedError – If the channel was built with
mtcf(the composition-resolved collision model) and no circuit: reconstructingsigma(r, t)after the fact would need the live collision-model evaluation at each recorded state, which isn’t exposed from Python yet – reconstructing it from a frozen table here would silently misrepresent the solve.
- joule_power(gap: float | None = None) numpy.ndarray#
Total Joule power dissipated in the channel cross-section.
P(t) = E(t) * gap * I(t) = U(t) * I(t)[W], i.e. the 1-D radial current profile fromtotal_current()extruded uniformly over the inter-electrode gap (the same constant-along-the-gap assumption astotal_current()).- Parameters:
gap (
floatorNone, optional) – Inter-electrode gap [m]. If None (default), uses thegappassed at construction.- Returns:
Joule power [W] at each recorded time in
t.- Return type:
- Raises:
ValueError – If no gap was supplied here or at construction.
- deposited_energy(gap: float | None = None) float#
Total deposited (Joule) energy over the run.
E_dep = \int_0^{t_{end}} E(t) * gap * I(t)\,dt[J], the time integral ofjoule_power()(trapezoidal rule over the recorded output times).
- channel_radius(method: str = 'half_max') numpy.ndarray#
Channel radius
R(t)[m] from the gas-temperature profile.- Parameters:
method (
{"half_max", "sigma_weighted"}) –half_max: radius whereTg - T_ambdrops to half its axis value.sigma_weighted:sqrt(<r^2>)weighted byTg - T_amb.
- Returns:
Channel radius [m] at each recorded time in
t.- Return type:
- channel_diameter(method: str = 'half_max') numpy.ndarray#
Channel diameter
D(t) = 2 R(t)[m].See
channel_radius()for themethodoptions; this is the quantity directly comparable to rizer’s engineering diffusion model (rizer.models.nrp.engineering_model.diffusion) and experiment.- Parameters:
method (
{"half_max", "sigma_weighted"}) – Passed through tochannel_radius().- Returns:
Channel diameter [m] at each recorded time in
t.- Return type: