rizer.adaptive_models.selector#

Model selection: indicators, modeling-error estimator, blend band.

Switch criteria are dimensionless numbers computed from existing state (the reservoir-AIM lesson): thermal nonequilibrium theta, ionization fraction, Damköhler number.

The ModelingErrorEstimator is the goal-oriented layer (Oden-Prudhomme): its physics surrogate sizes the terms a coarse candidate neglects, normalized by the QoI scale. Switches complete through a smooth BlendBand (the SBES lesson). ModelSelector resolves the active model from a graph of SwitchRule edges by live condition, not by position in a fixed sequence: at every step it matches candidate edges against whichever stage is currently active and arms whichever fires, with a dwell so the choice cannot chatter.

Attributes#

Classes#

ModelingErrorEstimator

Estimate a coarse candidate's per-step relative error on the QoIs.

BlendBand

A smooth \(C^1\) transition band on a normalized margin.

SwitchRule

One ladder edge: when to leave outgoing for incoming.

HandoffStep

One macro-step's outcome from the ladder driver.

ModelSelector

Drives the ladder: candidate matching and blend bookkeeping.

Functions#

theta(→ float)

Thermal nonequilibrium \(\theta = |T_e - T_g| / T_g\).

weighted_theta(→ float)

Electron-weighted nonequilibrium \(\theta_w = x_e \, \theta\).

electron_density(→ float)

Electron number density [m^-3] from the canonical state.

lte_validity_ratio(→ float)

Thermalization indicator \(x_{e,crit}/x_e\) (LTE valid at < 1).

damkohler(→ float)

Damköhler number \(Da = \tau \cdot \max_k |dY_k/dt| / Y_k\).

Module Contents#

rizer.adaptive_models.selector.logger#
rizer.adaptive_models.selector.theta(state: rizer.adaptive_models.state.PlasmaState) → float#

Thermal nonequilibrium \(\theta = |T_e - T_g| / T_g\).

rizer.adaptive_models.selector.weighted_theta(state: rizer.adaptive_models.state.PlasmaState, i_electron: int, molecular_weights: numpy.ndarray) → float#

Electron-weighted nonequilibrium \(\theta_w = x_e \, \theta\).

The goal-oriented form of theta(): it equals the relative shift of the number-weighted mean temperature, \(|T - T_g|/T_g\) with \(T = (n_h T_g + n_e T_e)/n_{tot}\) — i.e. the direct QoI error of the 2-T to 1-T collapse. A recombining plasma can hold a small raw \(\theta\) plateau indefinitely (elastic heating balancing inelastic/chemistry sinks) while the collapse error is ppm-level; \(\theta_w\) sees through that. .. warning:

**UNREVIEWED PHYSICS** (``rizer/adaptive_models/PHYSICS.md``,
section 6.1). The equations above and their implementation
have not been validated by a human: do not use results from this
trigger for design decisions or publication. Open findings: PHY-21.

Reviewed-by: nobody yet -- set the class attribute ``reviewed_by``
(:mod:`rizer.adaptive_models.review`) to sign off.
rizer.adaptive_models.selector.electron_density(state: rizer.adaptive_models.state.PlasmaState, i_electron: int, molecular_weights: numpy.ndarray) → float#

Electron number density [m^-3] from the canonical state.

rizer.adaptive_models.selector.lte_validity_ratio(state: rizer.adaptive_models.state.PlasmaState, i_electron: int, molecular_weights: numpy.ndarray, x_e_crit: float) → float#

Thermalization indicator \(x_{e,crit}/x_e\) (LTE valid at < 1).

\(x_e = n_e / N\) is the ionization fraction. Above a critical fraction the electron-heavy energy exchange is fast enough that the discharge column thermalizes within the pulse and a Local Thermodynamic Equilibrium (LTE) description becomes a good approximation — thermal equilibrium (\(T_e = T_g\)) and chemical equilibrium (composition at its equilibrium value, not evolved by finite-rate kinetics) both hold [Laux2026]. The limit of that regime is the fully ionized “thermal spark” of Minesi et al. [Minesi2020] (\(x_e \to 1\) in air at ambient density); the electron-gas equilibration criterion is investigated in detail by Maillard et al. (Laux group). A fraction rather than an absolute density keeps the criterion independent of the kernel’s density (pre-heating, dissociation). The indicator decreases through 1 as \(x_e\) grows, matching the selector’s decreasing-band convention.

Warning

UNREVIEWED PHYSICS (rizer/adaptive_models/PHYSICS.md, section 6.2). The equations above and their implementation have not been validated by a human: do not use results from this trigger for design decisions or publication.

Reviewed-by: nobody yet – set the class attribute reviewed_by (rizer.adaptive_models.review) to sign off.

References

[Laux2026]

C. O. Laux, J.-B. Perrin-Terrin, V. Lafaurie, S. M. Starikovskaia, “Foundations of plasma-assisted combustion: II. Mechanisms and applications”, Plasma Sources Sci. Technol. 35, 023002 (2026).

[Minesi2020]

N. Minesi, S. Stepanyan, P. Mariotto, G.-D. Stancu, C. O. Laux, “Fully ionized nanosecond discharges in air: the thermal spark”, Plasma Sources Sci. Technol. 29, 085003 (2020).

rizer.adaptive_models.selector.damkohler(state: rizer.adaptive_models.state.PlasmaState, plasma: cantera.Solution, tau: float) → float#

Damköhler number \(Da = \tau \cdot \max_k |dY_k/dt| / Y_k\).

Warning

UNREVIEWED PHYSICS (rizer/adaptive_models/PHYSICS.md, section 6.5). The equations above and their implementation have not been validated by a human: do not use results from this trigger for design decisions or publication. Open findings: PHY-04.

Reviewed-by: nobody yet – set the class attribute reviewed_by (rizer.adaptive_models.review) to sign off.

class rizer.adaptive_models.selector.ModelingErrorEstimator(plasma: cantera.Solution, collision_freq: rizer.transport.mixture_law.MixtureCollisionFrequencies)#

Estimate a coarse candidate’s per-step relative error on the QoIs.

The surrogate is the magnitude of the neglected source terms — the Te - Tg coupling power a 1-T candidate drops, the chemical source a chemistry-free candidate drops — normalized by the QoI scale rho * cv * T over the step tau.

Parameters:
plasma#
collision_freq#
neglected_coupling(state: rizer.adaptive_models.state.PlasmaState) → float#

Electron-heavies coupling power a 1-T candidate drops [W/m^3].

neglected_chemistry(state: rizer.adaptive_models.state.PlasmaState) → float#

Chemical power a chemistry-free candidate drops [W/m^3].

estimate(state: rizer.adaptive_models.state.PlasmaState, qoi: rizer.adaptive_models.contract.QoISet, tau: float, *, drops_second_temperature: bool = False, drops_chemistry: bool = False) → float#

Relative QoI error of a candidate over one step of length tau.

class rizer.adaptive_models.selector.BlendBand#

A smooth \(C^1\) transition band on a normalized margin.

The band spans indicator values from threshold * (1 + width) (band entry, margin 0) down to threshold (hand-off complete, margin 1). The blend weight is the smoothstep \(w(m) = 3m^2 - 2m^3\).

Warning

UNREVIEWED PHYSICS (rizer/adaptive_models/PHYSICS.md, section 7). The equations above and their implementation have not been validated by a human: do not use results from this trigger for design decisions or publication. Open findings: PHY-17.

Reviewed-by: nobody yet – set the class attribute reviewed_by (rizer.adaptive_models.review) to sign off.

Notes

\(w\) is \(C^1\): both \(w\) and \(w'(m) = 6m(1 - m)\) vanish at the band’s two endpoints (\(m=0\), \(m=1\)), so the blended state’s value and rate of change are continuous across the hand-off – no kink where the incoming model’s weight ramps from 0 to 1. It is the unique cubic fixed by \(w(0)=0, w(1)=1, w'(0)=0, w'(1)=0\) – the lowest-degree polynomial giving \(C^1\) continuity, monotonic and bounded to \([0, 1]\) with no overshoot.

width: float = 0.1#
static weight(margin: float) → float#

Smoothstep weight of the incoming model, in [0, 1].

margin(indicator: float, threshold: float) → float | None#

Return the normalized margin for a decreasing indicator.

Returns None outside the band (indicator above band entry), 0 at band entry, 1 when the indicator reached threshold.

class rizer.adaptive_models.selector.SwitchRule#

One ladder edge: when to leave outgoing for incoming.

Which model applies is decided by which edge’s condition is currently satisfied, evaluated fresh every step — there is no notion of ladder position. Several rules may share the same outgoing (a stage can have more than one compatible next model; whichever condition fires first, or has the largest margin if more than one fires the same step, wins).

Parameters:
  • outgoing (str) – Stage names of the edge. outgoing="*" matches any active stage that does not expose energy_input_active (the field-rebound wildcard: any relaxation-tier stage, none of the deposition tiers).

  • incoming (str) – Stage names of the edge. outgoing="*" matches any active stage that does not expose energy_input_active (the field-rebound wildcard: any relaxation-tier stage, none of the deposition tiers).

  • indicator (typing.Callable) – (state, t) -> float | None — None while the guard is not armed (e.g. pulse still on). For blend=True rules this is a decreasing indicator (fires through threshold from above); for blend=False rules it is increasing (fires at indicator >= threshold).

  • threshold (float) – Indicator value at which the hand-off fires.

  • operators (list of TransitionOperator) – Applied in order to the outgoing state to seed the incoming stage.

  • blend (bool) – True (default): a smooth BlendBand hand-off over several macro steps, gated by dwell_steps. False: an immediate, one-step switch once the indicator crosses threshold — for edges that are physically abrupt (e.g. a new field arriving mid-relaxation), not a gradual regime change; not gated by dwell, and pre-empts any in-progress blend.

outgoing: str#
incoming: str#
indicator: Callable[[rizer.adaptive_models.state.PlasmaState, float], float | None]#
threshold: float#
operators: Sequence[rizer.adaptive_models.transitions.TransitionOperator] = ()#
blend: bool = True#
matches(active: Any) → bool#

Whether this edge is a candidate for the currently active stage.

class rizer.adaptive_models.selector.HandoffStep#

One macro-step’s outcome from the ladder driver.

Returned by ModelSelector.advance(); the composite’s time-integration loop only ever reads this — the mutation decision itself (which edge, blend weight, hand-off bookkeeping) is entirely internal to the selector.

state: rizer.adaptive_models.state.PlasmaState#
stage_label: str#
switched_to: str | None = None#
class rizer.adaptive_models.selector.ModelSelector(rules: Sequence[SwitchRule], estimator: ModelingErrorEstimator, stages: dict[str, Any], qoi: rizer.adaptive_models.contract.QoISet, plasma: cantera.Solution, blend: bool = True, band: BlendBand | None = None, dwell_steps: int = 5)#

Drives the ladder: candidate matching and blend bookkeeping.

“The ladder” is the graph of interchangeable stage solvers (increasing fidelity: dimensionality, temperature count, chemistry, EEDF closure) this module switches between – see rizer/adaptive_models/ARCHITECTURE.md for the full design and layer diagram.

Warning

UNREVIEWED PHYSICS (rizer/adaptive_models/PHYSICS.md, section 7). The equations above and their implementation have not been validated by a human: do not use results from this trigger for design decisions or publication. Open findings: PHY-17, PHY-18.

Reviewed-by: nobody yet – set the class attribute reviewed_by (rizer.adaptive_models.review) to sign off.

Parameters:
  • rules (list of SwitchRule) – All ladder edges (unordered — see SwitchRule).

  • estimator (ModelingErrorEstimator) – Used to report the incoming candidate’s modeling error at each seam.

  • stages (dict of str to BasePhysicalModel) – All stages by name, for looking up an edge’s incoming target.

  • qoi (tuple of str) – Forwarded to modeling_error at each armed hand-off.

  • plasma (cantera.Solution) – Shared Cantera plasma object, needed by TransitionOperator and the blend (density/volume/radius are recomputed from the EOS, not blended).

  • blend (bool) – Blend hand-offs over a smooth band (default). False hard-swaps every blend=True rule too (blend=False rules are always sharp regardless of this flag).

  • band (BlendBand) – Shared transition band.

  • dwell_steps (int) – Minimum macro steps in a stage before its blend=True exit rules are evaluated (anti-chatter). Does not gate blend=False rules, which must react immediately.

reviewed_by: str | None = None#
rules#
estimator#
band#
dwell_steps = 5#
active_stage: Any = None#
advance(state: rizer.adaptive_models.state.PlasmaState, t: float) → HandoffStep#

Gather candidate edges for the active stage; arm/track/complete.

No ladder position: candidates are found by matching each rule against whichever stage is currently active, fresh every call. blend=False (sharp) candidates are checked first and pre-empt everything, every step, regardless of dwell or an in-progress blend. Otherwise, once no candidate is armed the driver keeps tracking the currently-armed rule (not re-scanning candidates) until its blend band completes.