Skip to content

Hybrid Systems

This page covers the public API of flowcean.hybrid and its subpackages:

  • Modeling, Simulation, and Plotting: system definitions, automaton diagrams, trajectories, explicit sampling, plotting, and the opt-in plot_style() context for custom Matplotlib figures.
  • Benchmarks: reusable system factories.
  • HyDRA identification: HyDRALearner returns fully assigned LearnedFlows or raises HyDRAIdentificationError with the failing segment. Use to_labeled_frames for selector training or to_flow_ids for NumPy assignments. Progress observers use HyDRACallback or PlotCallback; selector helpers are also available from flowcean.hybrid.hydra.selector.
  • Dainarx identification: fit_dainarx_dynamics discovers sampled NARX dynamics; fit_dainarx_transitions learns guards and post-switch predictors. fit_dainarx composes both stages. See the Dainarx example.
  • FaMoS-DTW identification: independent partial location discovery, sampled affine ARX fitting, and source-conditioned next-location trees. See the FaMoS example.
  • MMSR identification: coupled scalar symbolic regression and separate soft-membership transition modeling, with inspectable mappings and guards. See the MMSR example.

See the hybrid systems guide for modeling concepts and the evaluation guide for selecting observations and scoring predictions with existing tools.

Modeling, Simulation, and Plotting

Hybrid-system modeling, automaton diagrams, simulation, and traces.

Attributes

Input module-attribute

Input = np.ndarray

InputStream module-attribute

InputStream = Callable[[float], Input]

Parameters module-attribute

Parameters = Mapping[str, float]

Classes

CrossingDirection

Bases: IntEnum

Direction in which an event surface must cross zero.

Attributes
FALLING class-attribute instance-attribute
FALLING = -1
EITHER class-attribute instance-attribute
EITHER = 0
RISING class-attribute instance-attribute
RISING = 1

DelayFunction

Bases: Protocol

Transition delay sampled at each accepted detection, not at execution.

The transition's scheduling policy determines which detections are accepted and which sampled occurrence can execute. Each sample freezes that occurrence's deadline at detection time plus the returned duration. Return a finite, nonnegative scalar duration in model time units. The source location's state, effective parameters, and residence time are supplied at detection. See FlowFunction for shared inputs, supported signatures, and purity requirements.

EventSurface dataclass

EventSurface(fn: Callable[..., float], direction: CrossingDirection = EITHER, label: str | None = None)

Scalar event surface defining a simulated transition event.

Supply a continuous scalar function whose zero crossings identify the switching boundary. RISING accepts negative-to-positive crossings, FALLING positive-to-negative crossings, and EITHER both. Exact zero on entry follows the transition's SurfaceEntryPolicy. NaN values raise InvalidEventSurfaceValueError; nonzero values, including infinities, retain their sign in entry checks. Continuous crossing detection brackets roots using integration-step endpoint signs. Multiple crossings within a step can be missed; tangency detection depends on the step endpoints.

Parameters:

Name Type Description Default
fn Callable[..., float]

Root function. See FlowFunction for callback inputs.

required
direction CrossingDirection

Crossing direction. Defaults to either direction.

EITHER
label str | None

Optional display label.

None
Attributes
fn instance-attribute
fn: Callable[..., float]
direction class-attribute instance-attribute
label class-attribute instance-attribute
label: str | None = None

EventSurfaceFunction

Bases: Protocol

Scalar event-surface callback.

See FlowFunction for shared inputs and supported callback signatures.

Flow dataclass

Flow(fn: Callable[..., Derivative], label: str | None = None)

Reusable continuous-state derivative law.

Parameters:

Name Type Description Default
fn Callable[..., Derivative]

Function returning the state derivative. See FlowFunction for callback inputs. Scalar derivative returns are accepted only for single-state systems, both during solver evaluation and when derivatives are explicitly sampled from a trajectory.

required
label str | None

Optional display label.

None
Attributes
fn instance-attribute
fn: Callable[..., Derivative]
label class-attribute instance-attribute
label: str | None = None

FlowFunction

Bases: Protocol

Continuous-state derivative callback.

Flow, event-surface, reset, and delay callbacks share these inputs: physical time (t), the continuous state vector, effective parameters, an input_stream returning a vector for a requested time, and the current visit's elapsed location_time.

Model callbacks may declare any subset using these names, including keyword-only arguments. Named callbacks with **kwargs receive all five inputs. Four-positional callbacks receive (t, state, parameters, input_stream); positional-only arguments, *args, and noncanonical required names select that form.

Callbacks and input streams must be pure and deterministic: integration and derivative sampling can evaluate the same inputs repeatedly.

HybridSystem dataclass

HybridSystem(locations: Sequence[Location], transitions: Sequence[Transition], initial_location: Location, initial_state: State, parameters: Parameters = dict())

Hybrid system with locations and transitions.

Parameters:

Name Type Description Default
locations Sequence[Location]

Location objects in this system.

required
transitions Sequence[Transition]

Transition list defining event surfaces and resets.

required
initial_location Location

Starting location object.

required
initial_state State

Initial state vector.

required
parameters Parameters

Global parameter map passed to callbacks.

dict()
Attributes
locations instance-attribute
locations: Sequence[Location]
transitions instance-attribute
transitions: Sequence[Transition]
initial_location instance-attribute
initial_location: Location
initial_state instance-attribute
initial_state: State
parameters class-attribute instance-attribute
parameters: Parameters = field(default_factory=dict)
Methods:
transitions_from
transitions_from(location: Location) -> list[Transition]

Return transitions leaving the given location.

Location dataclass

Location(flow: Flow, *, label: str | None = None, parameters: Parameters | None = None)
Location(flow: Callable[..., Derivative], *, label: str | None = None, parameters: Parameters | None = None)
Location(flow: Flow | Callable[..., Derivative], *, label: str | None = None, parameters: Parameters | None = None)

Discrete hybrid-automaton location.

Parameters:

Name Type Description Default
flow Flow | Callable[..., Derivative]

Flow definition or bare derivative callback active here.

required
label str | None

Optional display label.

None
parameters Parameters | None

Location-local parameters overriding system parameters with the same names. Effective maps are snapshotted for each run.

None
Attributes
flow instance-attribute
flow: Flow
label instance-attribute
label: str | None
parameters instance-attribute
parameters: Parameters

Reset dataclass

Reset(fn: Callable[..., State], label: str | None = None)

State reset applied on a transition.

The callback receives the source location's effective parameters and residence time. Its result must match the continuous-state dimension; a scalar is accepted for a single-state system.

Parameters:

Name Type Description Default
fn Callable[..., State]

Reset function applied at the event time. See FlowFunction for callback inputs.

required
label str | None

Optional display label.

None
Attributes
fn instance-attribute
fn: Callable[..., State]
label class-attribute instance-attribute
label: str | None = None

ResetFunction

Bases: Protocol

State reset callback.

See FlowFunction for shared inputs and supported callback signatures.

SurfaceEntryPolicy

Bases: StrEnum

Behavior when a transition surface is exactly zero on location entry.

Entry includes initialization and arrival after a transition. ERROR raises SurfaceEntryError; TRIGGER detects the transition immediately (starting its delay); CONTINUE begins continuous integration with the surface at zero. For CONTINUE, choose a flow that departs in the direction opposite to the accepted crossing so integration can advance.

All outgoing surfaces are evaluated before resolving entry. NaN values are rejected first, then zero ERROR surfaces. Exactly one zero-delay TRIGGER surface at zero performs a jump; multiple such surfaces raise AmbiguousTransitionError. Positive-delay TRIGGER surfaces schedule execution without a jump. Entry handling repeats at the target after an immediate transition.

Attributes
ERROR class-attribute instance-attribute
ERROR = 'error'
TRIGGER class-attribute instance-attribute
TRIGGER = 'trigger'
CONTINUE class-attribute instance-attribute
CONTINUE = 'continue'

Transition dataclass

Transition(source: Location, target: Location, event_surface: EventSurface | Callable[..., float], reset: Reset | Callable[..., State] | None = None, *, entry_policy: SurfaceEntryPolicy = ERROR, delay: float | Callable[..., float] = 0.0, scheduling_policy: TransitionSchedulingPolicy = FIRST_DETECTION)

Discrete event-triggered transition between locations.

event_surface is a scalar zero-crossing surface.

Parameters:

Name Type Description Default
source Location

Source location.

required
target Location

Target location.

required
event_surface EventSurface | Callable[..., float]

Event surface that triggers the transition.

required
reset Reset | Callable[..., State] | None

Optional reset applied upon transition.

None
entry_policy SurfaceEntryPolicy

Behavior when the event surface is exactly zero upon entry to the source location.

ERROR
delay float | Callable[..., float]

Fixed finite, nonnegative scalar duration, or a pure, deterministic callback returning one in the model's time units. See DelayFunction for inputs. The callback is evaluated at detection, including entry triggers, and validated then; fixed values are validated on construction. Each accepted detection freezes an occurrence's deadline at detection time plus delay. The source flow and residence clock continue until execution; the reset receives that execution context. Zero (the default) requests immediate execution.

0.0
scheduling_policy TransitionSchedulingPolicy

How repeated detections affect this transition's pending occurrence. FIRST_DETECTION (the default) accepts only the first detection for the entire visit, even if it loses to another candidate. LATEST_DETECTION replaces the occurrence on every crossing, advancing or postponing its deadline. EACH_DETECTION samples every crossing and keeps the earliest deadline; equal own deadlines retain the earliest detection time. Every exit, including a self-transition, cancels all occurrences. The earliest outgoing deadline executes. At an exactly equal numerical time, direction-qualified detections are processed before execution; equal earliest deadlines of distinct transitions are ambiguous. No tolerance groups nearby times.

FIRST_DETECTION
Attributes
source instance-attribute
source: Location
target instance-attribute
target: Location
event_surface instance-attribute
event_surface: EventSurface
reset class-attribute instance-attribute
reset: Reset | None = None
entry_policy class-attribute instance-attribute
delay class-attribute instance-attribute
delay: float | Callable[..., float] = 0.0
scheduling_policy class-attribute instance-attribute

TransitionSchedulingPolicy

Bases: StrEnum

How detections schedule a transition during one source-location visit.

FIRST_DETECTION accepts only the first detection, even when another transition currently has an earlier deadline. LATEST_DETECTION accepts every direction-qualified crossing and replaces this transition's pending occurrence. EACH_DETECTION samples an independent occurrence at each crossing; the earliest deadline wins, with equal deadlines retaining the earliest detection time. Leaving the visit cancels all occurrences.

Attributes
FIRST_DETECTION class-attribute instance-attribute
FIRST_DETECTION = 'first_detection'
LATEST_DETECTION class-attribute instance-attribute
LATEST_DETECTION = 'latest_detection'
EACH_DETECTION class-attribute instance-attribute
EACH_DETECTION = 'each_detection'

AmbiguousTransitionError

Bases: HybridSimulationError

Raised for competing entry-time jumps or equal scheduled deadlines.

HybridSimulationError

Bases: RuntimeError

Base class for hybrid simulation runtime failures.

InvalidEventSurfaceValueError

InvalidEventSurfaceValueError(transitions: Sequence[Transition], time: float, *, context: str)

Bases: HybridSimulationError

Raised when an event surface returns NaN.

SimulationProgressError

Bases: HybridSimulationError

Raised when a solver event does not advance physical time.

SurfaceEntryError

Bases: HybridSimulationError

Raised when an ERROR surface is zero on location entry.

ContinuousSegment dataclass

ContinuousSegment(location: Location, t_span: tuple[float, float], _solution: Callable[[ndarray], ndarray], _clock: _ResidenceClock, _knots: ndarray)

Continuous evolution in one location over a time interval.

The solver's dense solution and plotting knots are private. The residence clock stays anchored throughout this visit, independent of sampling grids.

Attributes
location instance-attribute
location: Location
t_span instance-attribute
t_span: tuple[float, float]
Methods:
evaluate
evaluate(time: float) -> TrajectoryPoint

Evaluate within the inclusive interval without invoking callbacks.

The start is after any preceding jump chain; the end is before any following jump. Times must be finite; extrapolation is not allowed.

location_time
location_time(time: float) -> float

Return residence age without evaluating the continuous state.

Event dataclass

Event(time: float, transition: Transition, state_before: State, state_after: State, microstep: int, location_time_before: float, detection_time: float)

A recorded transition with states immediately before and after it.

Pre- and post-reset state snapshots are detached and read-only. transition references the model's transition, including its source, target, event surface, and reset. time is physical simulation time; microstep orders events at that time, starting at zero. location_time_before is the age of the source visit before the jump. detection_time identifies the detection that scheduled the executed occurrence, possibly on location entry: the first for FIRST_DETECTION, the latest for LATEST_DETECTION, or the earliest-deadline occurrence for EACH_DETECTION (earliest detection when its own deadlines tie). time is when the transition executed after its sampled delay. They are equal for zero-delay transitions. Unexecuted detections are not recorded as events.

Attributes
time instance-attribute
time: float
transition instance-attribute
transition: Transition
state_before instance-attribute
state_before: State
state_after instance-attribute
state_after: State
microstep instance-attribute
microstep: int
location_time_before instance-attribute
location_time_before: float
detection_time instance-attribute
detection_time: float

HybridTrajectory dataclass

HybridTrajectory(system: HybridSystem, t_span: tuple[float, float], initial_state: State, initial_location: Location, initial_location_time: float, execution: tuple[ContinuousSegment | Event, ...], _bindings: _RunBindings)

A run's ordered execution, associated model, and parameter snapshots.

Numerical snapshots are read-only. Model identity is retained, but later model parameter edits do not affect this run. Callbacks and input streams must remain deterministic and pure when explicitly resampled.

Attributes
system instance-attribute
system: HybridSystem
t_span instance-attribute
t_span: tuple[float, float]
initial_state instance-attribute
initial_state: State
initial_location instance-attribute
initial_location: Location
initial_location_time instance-attribute
initial_location_time: float
execution instance-attribute
execution: tuple[ContinuousSegment | Event, ...]
segments property
segments: tuple[ContinuousSegment, ...]

Continuous pieces in execution order.

events property
events: tuple[Event, ...]

Individual transitions, including every same-time microstep.

parameters property
parameters: Mapping[Location, Parameters]

Read-only effective parameter snapshots for all declared locations.

Methods:
evaluate
evaluate(time: float) -> TrajectoryPoint

Evaluate a finite time in the inclusive run interval, without callbacks.

At an exact event time, return the final state and location after the entire jump chain, with zero residence age. This includes initial and final events.

sample
sample(times: Iterable[float] | None = None, *, dt: float | None = None, include_state: bool = True, include_location_id: bool = True, include_location_label: bool = False, include_location_time: bool = True, include_inputs: bool = False, include_derivatives: bool = False) -> DataFrame

Build a frame at requested times, using post-transition event values.

At an event time, the row describes the state after the entire same-time chain, as returned by evaluate. Default sampling invokes no callbacks. Inputs and flow derivatives are evaluated at the requested times when included; these callbacks and input streams must remain pure and deterministic.

Parameters:

Name Type Description Default
times Iterable[float] | None

Finite, non-descending times within the trajectory's interval. Accepts generators, duplicates, and an empty grid. Supply exactly one of times or dt.

None
dt float | None

Positive finite grid spacing. Includes the final endpoint; a zero-duration run produces one row.

None
include_state bool

Include continuous-state columns x0, x1, ...

True
include_location_id bool

Include location_id, the index of the active location in the system's declaration order.

True
include_location_label bool

Include the display location_label.

False
include_location_time bool

Include elapsed visit time as location_time.

True
include_inputs bool

Evaluate the input stream and include u0, u1, ... . Requires a stream and a nonempty grid; the input width must be constant across requested times.

False
include_derivatives bool

Evaluate the active flow with the sampled state and run parameters, including dx0, dx1, ... .

False

Returns:

Type Description
DataFrame

A Polars frame with physical time t and the selected columns.

DataFrame

Empty grids retain the known state, derivative, and location

DataFrame

schemas. Use Polars to select or rename columns and add row indices.

Raises:

Type Description
ValueError

Grid arguments, input capture, or callback outputs are invalid.

TrajectoryPoint dataclass

TrajectoryPoint(state: State, location: Location, location_time: float)

State, location, and residence age at one point in an execution.

The state is a detached, read-only array; location is the active model object.

Attributes
state instance-attribute
state: State
location instance-attribute
location: Location
location_time instance-attribute
location_time: float

Functions:

build_hybrid_system_dot

build_hybrid_system_dot(system: HybridSystem, *, location_colors: Mapping[Location, str] | None = None, show_event_labels: bool = True, show_reset_labels: bool = True, show_direction: bool = False, show_entry_policy: bool = False) -> str

Export the complete declared automaton as deterministic DOT text.

Locations and transitions retain declaration order, including isolated locations, parallel transitions, and self-loops. A point and incoming arrow mark the initial location. Node IDs are positional, independent of display labels; reordering locations changes their IDs.

Labels follow the model's display-label precedence: explicit labels, then callback names, then positional fallbacks for unnamed objects. Labels are treated as literal text, not DOT markup or mathematical expressions. No simulation or callback evaluation is performed, and Graphviz need not be installed. Positive fixed delays are annotated in model time units; delay callbacks are annotated by name, without evaluation.

Parameters:

Name Type Description Default
system HybridSystem

Constructed hybrid system to visualize.

required
location_colors Mapping[Location, str] | None

Optional colors keyed by Location identity, using Matplotlib color names or hex values, as in native plots. Defaults follow this system's declaration order; matching colors across different artifacts do not establish corresponding locations.

None
show_event_labels bool

Annotate transitions with event-surface labels.

True
show_reset_labels bool

Annotate transitions that have resets with labels.

True
show_direction bool

Include the event surface's zero-crossing direction.

False
show_entry_policy bool

Include each transition's surface-entry policy.

False

Returns:

Type Description
str

DOT source suitable for saving or passing to render_dot_svg.

render_dot_svg

render_dot_svg(dot_source: str) -> str

Render DOT text as SVG using Graphviz's external dot executable.

No files are written and no viewer is opened. SVG layout can vary between Graphviz versions even when the input DOT is identical.

Parameters:

Name Type Description Default
dot_source str

DOT source to render.

required

Returns:

Type Description
str

SVG text decoded as UTF-8.

Raises:

Type Description
RuntimeError

Graphviz is not installed or rejects the DOT source.

plot_locations

plot_locations(trajectory: HybridTrajectory, *, location_colors: Mapping[Location, str] | None = None, show_labels: bool = False, alpha: float = BAND_ALPHA, ax: Axes | None = None) -> Axes

Shade actual positive-duration segments, using Location identity.

plot_state_space

plot_state_space(trajectory: HybridTrajectory, x_dim: int = 0, y_dim: int = 1, *, location_colors: Mapping[Location, str] | None = None, show_location_legend: bool = True, show_event_points: bool = False, ax: Axes | None = None) -> Axes

Plot two selected continuous state coordinates, with time implicit.

Each continuous segment is drawn separately so reset jumps are not joined. show_event_points optionally marks states on both sides of transitions; switches without resets are unmarked by default. Returns the axes without showing, saving, or closing the figure.

plot_style

plot_style() -> Iterator[None]

Temporarily use hybrid plotting defaults for custom Matplotlib figures.

Use with plot_style(): or decorate an application function with @plot_style(). Defaults cover typography, lines, categorical colors, legends, backgrounds, and a light grid. Ordinary Matplotlib artist arguments can override them. Existing artists are not restyled.

Matplotlib rc settings are restored on exit, including exceptional exit. This context neither selects nor restores a backend and never draws, shows, saves, or closes figures. As with Matplotlib's rc_context, settings are process-wide while the explicit context is active. Library renderers apply their own local styling and do not enter this context.

plot_trajectory

plot_trajectory(trajectory: HybridTrajectory, dims: Sequence[int] | None = None, *, location_colors: Mapping[Location, str] | None = None, show_locations: bool = True, show_location_labels: bool = False, show_events: bool = True, show_event_labels: bool = True, show_event_points: bool = False, ax: Axes | None = None) -> Axes

Plot state coordinates against time without lines across resets.

show_events controls vertical transition indicators and labels; show_event_points separately marks the states before and after each transition. Markers are off by default, including at continuous switches. Returns the axes; the caller owns showing, saving, and closing the figure.

simulate

simulate(system: HybridSystem, t_span: tuple[float, float], x0: Iterable[float] | None = None, location0: Location | None = None, *, input_stream: InputStream | None = None, initial_location_time: float = 0.0, max_jumps: int = 256, rtol: float = 1e-07, atol: float = 1e-09, max_step: float | None = None) -> HybridTrajectory

Integrate a hybrid execution with dense continuous segments.

Sampling is a separate operation on the returned trajectory. Callbacks and input streams must be pure and deterministic under repeated evaluation. Equal endpoints resolve entry transitions without calling the ODE solver. Parameters are snapshotted for every location at the start of the run. Each transition's scheduling policy selects accepted detections and pending occurrences. Delay callbacks run once per accepted detection, freezing that occurrence's deadline. A positive delay keeps the source flow and residence clock active until execution. Leaving the source visit cancels all its occurrences. At exactly equal numerical times, all direction-qualified detections precede execution, even at the final endpoint; a latest detection can postpone execution beyond the run. Distinct transitions with equal earliest deadlines are ambiguous.

Root detection uses integration-step endpoint signs: multiple roots within a step can be missed, and tangencies are step-dependent. Equality means exact numerical equality, not mathematical simultaneity or a tolerance.

Parameters:

Name Type Description Default
system HybridSystem

Model to simulate.

required
t_span tuple[float, float]

Finite start and end times, with end at or after start.

required
x0 Iterable[float] | None

Initial continuous state, overriding the model's initial state.

None
location0 Location | None

Initial location, overriding the model's initial location.

None
input_stream InputStream | None

Function returning a one-dimensional input vector at a requested physical time.

None
initial_location_time float

Finite, nonnegative age of the initial visit. Each subsequent transition begins a new visit at age zero. Each run starts with no pending transition.

0.0
max_jumps int

Maximum number of transitions, including same-time chains.

256
rtol float

Relative tolerance for SciPy integration.

1e-07
atol float

Absolute tolerance for SciPy integration.

1e-09
max_step float | None

Maximum integration step; None uses SciPy's default.

None

Returns:

Type Description
HybridTrajectory

The trajectory, retaining its supplied initial condition and ordered

HybridTrajectory

continuous segments and events.

Raises:

Type Description
HybridSimulationError

The transition limit is exceeded or a subclass reports an invalid surface, ambiguous entry, or progress failure.

ValueError

Time bounds, initial conditions, or callback outputs are invalid.

Benchmarks

Reusable hybrid-system benchmark factories.

Functions:

wind_turbine_power

wind_turbine_power(rotor_speeds: Sequence[float] | ndarray, location_labels: Sequence[str], *, parameters: Parameters) -> ndarray

Return generator-shaft mechanical power (W) for each rotor speed.

rotor_speeds are positive angular speeds in rad/s; each corresponding location_labels entry selects the active torque law. The location matters in hysteresis bands, where speed alone does not select a law. parameters are those of the simulated turbine, including its gearbox ratio. The resulting one-dimensional array contains one power value per input speed, or no values for empty inputs. This is not electrical output; rated shaft power is not an instantaneous cap in other modes.

System factories

bouncing_ball

bouncing_ball(gravity: float = 9.81, restitution: float = 0.8, initial_state: ndarray | None = None) -> HybridSystem

Create a bouncing ball benchmark system.

Parameters:

Name Type Description Default
gravity float

Downward acceleration.

9.81
restitution float

Velocity multiplier on bounce.

0.8
initial_state ndarray | None

Optional initial [height, velocity].

None

Returns:

Type Description
HybridSystem

HybridSystem configured for a bouncing ball.

buck_converter

buck_converter(source_voltage: float = 24.0, inductance: float = 0.00265, capacitance: float = 0.0022, load_resistance: float = 10.0, *, inductor_resistance: float = 0.52, switch_resistance: float = 0.2, voltage_high: float = 12.1, voltage_low: float = 11.9, initial_state: Sequence[float] | ndarray | None = None) -> HybridSystem

Create a physical hysteretic nonsynchronous buck converter.

A buck converter is a step-down DC-DC power circuit. A controlled switch connects a constant-voltage supply to an inductor, which feeds a capacitor and resistive load in parallel. The inductor stores magnetic energy and prevents abrupt current changes; the capacitor stores electrical energy and smooths the output voltage. A freewheel diode provides a current path when the switch opens. Nonsynchronous means this diode, rather than a second controlled switch, carries the freewheeling current.

The state is [inductor_current, capacitor_voltage] in amperes and volts. The capacitor voltage is also the load voltage. Three locations describe the circuit's conduction states:

  • switch_on: The source supplies energy through the closed switch. Inductor current typically rises, feeding the load and capacitor.
  • switch_off: The source is disconnected, but inductor current continues through the diode. Stored magnetic energy feeds the output while current falls; the capacitor can still charge during this phase.
  • zero_current: Once inductor current reaches zero, the diode blocks reverse current. Only the capacitor supplies the load, so its voltage decays exponentially with time constant load_resistance * capacitance.

Hysteretic control opens the switch when voltage rises to voltage_high and closes it when voltage falls to voltage_low. Between thresholds, the switch retains its state. Switching is state-triggered, not driven by a fixed-frequency clock. The switch may close before current reaches zero, skipping zero_current. The thresholds are switching commands, not hard output-voltage bounds: stored inductor energy can produce overshoot.

The flows follow Kirchhoff's laws and the relations v_L = L * di/dt and i_C = C * dv/dt. With current i, output voltage v, supply V_s, inductance L, capacitance C, load resistance R, inductor resistance r_L, and closed-switch resistance r_S:

  • Switch on: di/dt = (V_s - (r_L + r_S) * i - v) / L.
  • Switch off: di/dt = (-r_L * i - v) / L.
  • In either conducting state: dv/dt = (i - v / R) / C.
  • Zero current: di/dt = 0 and dv/dt = -v / (R * C).

Each location has affine continuous dynamics; voltage and current events select the active circuit configuration. The diode is ideal, with no forward voltage drop or reverse current. Switching delays and diode recovery are not modeled; resistive losses are included in r_L, r_S, and the load.

On entry to zero_current, current is reset exactly to zero to remove numerical event-localization residuals, not to model a physical impulse. Lower-threshold voltage residuals within event-time localization precision are also snapped to the threshold, allowing coincident events to settle without an artificial integration step.

For a nonzero initial current, the latch starts on below voltage_high and off at or above it. At zero current, it starts in zero_current above voltage_low and otherwise starts on. A useful nominal simulation horizon is 0.02 seconds.

Parameters:

Name Type Description Default
source_voltage float

Constant input supply voltage in volts.

24.0
inductance float

Inductance in henries.

0.00265
capacitance float

Output capacitance in farads.

0.0022
load_resistance float

Resistive load in ohms.

10.0
inductor_resistance float

Inductor series resistance in ohms.

0.52
switch_resistance float

Closed-switch resistance in ohms.

0.2
voltage_high float

Rising output-voltage threshold that switches off.

12.1
voltage_low float

Falling output-voltage threshold that switches on.

11.9
initial_state Sequence[float] | ndarray | None

Initial [inductor_current, capacitor_voltage].

None

Returns:

Type Description
HybridSystem

HybridSystem configured for hysteretic buck conversion.

duffing_oscillator

duffing_oscillator(damping_high: float = 0.5, damping_low: float = 0.2, *, linear_stiffness: float = -1.0, cubic_stiffness_high: float = 1.5, cubic_stiffness_low: float = 0.5, inner_radius: float = 0.8, outer_radius: float = 1.2, velocity_retention: float = 0.95, initial_state: ndarray | None = None) -> HybridSystem

Create a normalized unit-mass oscillator with hysteretic switching.

The state has shape (2,) and order [position, velocity]; its derivative is [velocity, acceleration]. Supply an explicit finite input of shape (1,), [force], where force is applied force per unit mass (acceleration). Position and time use normalized units, not a prescribed SI scale. In location q, the acceleration is force - damping_q * velocity - linear_stiffness * position - cubic_stiffness_q * position**3. Both damping and cubic stiffness change with the location.

A falling crossing of position**2 - inner_radius**2 switches from high_damping to low_damping. A rising crossing of position**2 - outer_radius**2 switches back. Both resets preserve position and multiply velocity by velocity_retention without reversing it. These are native zero-crossing events, not urgent inequality guards; exact zero on entry uses the default SurfaceEntryPolicy.ERROR.

Initialization selects low_damping at or inside the inner radius and high_damping elsewhere, including the hysteresis band. This convention does not execute a guard. Simulation overrides x0 and location0 retain the simulator's usual entry and crossing semantics.

The default coefficients and boundaries follow Example 1, Figure 2(a) in the published model.

Parameters:

Name Type Description Default
damping_high float

High-location damping, in inverse time units. Must be finite, nonnegative, and at least damping_low.

0.5
damping_low float

Low-location damping, in inverse time units. Must be finite and nonnegative.

0.2
linear_stiffness float

Shared signed linear restoring coefficient, in inverse time squared. Must be finite; negative values destabilize the origin while the positive cubic term limits excursions.

-1.0
cubic_stiffness_high float

Positive finite high-location cubic restoring coefficient, in inverse position squared per time squared.

1.5
cubic_stiffness_low float

Positive finite low-location cubic restoring coefficient, in inverse position squared per time squared.

0.5
inner_radius float

Positive finite inward switching radius in position units.

0.8
outer_radius float

Finite outward switching radius, larger than the inner radius, in position units.

1.2
velocity_retention float

Finite velocity multiplier between zero and one.

0.95
initial_state ndarray | None

Optional finite [position, velocity] vector of shape (2,). If omitted, starts at the positive outer radius at rest.

None

Returns:

Type Description
HybridSystem

HybridSystem with high- and low-damping locations.

hybrid_oscillator

hybrid_oscillator(damping_left: float = 0.2, damping_right: float = 0.1, frequency: float = 2.0, initial_state: ndarray | None = None) -> HybridSystem

Create a piecewise damped oscillator benchmark.

Parameters:

Name Type Description Default
damping_left float

Damping for the left half-space.

0.2
damping_right float

Damping for the right half-space.

0.1
frequency float

Oscillation frequency.

2.0
initial_state ndarray | None

Optional initial [position, velocity].

None

Returns:

Type Description
HybridSystem

HybridSystem configured for a hybrid oscillator.

impact_oscillator

impact_oscillator(damping: float = 0.1, stiffness: float = 4.0, *, restitution: float = 0.7, initial_state: ndarray | None = None) -> HybridSystem

Create an impact oscillator driven by an external force.

Simulate with an explicit finite one-element input vector [force].

Parameters:

Name Type Description Default
damping float

Linear damping coefficient.

0.1
stiffness float

Spring stiffness.

4.0
restitution float

Velocity multiplier on impact.

0.7
initial_state ndarray | None

Optional initial [position, velocity].

None

Returns:

Type Description
HybridSystem

HybridSystem configured for an impact oscillator.

location_cycle

location_cycle(location_count: int = 4, dimension: int = 4, dwell_time: float = 0.5, initial_state: ndarray | None = None) -> HybridSystem

Create a scalable hybrid system that cycles through locations.

The system has location_count locations, each with linear dynamics active for dwell_time. Transitions leave the continuous state unchanged.

Parameters:

Name Type Description Default
location_count int

Number of locations in the cycle.

4
dimension int

Dimension of the continuous state.

4
dwell_time float

Time to stay in each location.

0.5
initial_state ndarray | None

Optional initial state (length dimension).

None

Returns:

Type Description
HybridSystem

HybridSystem cycling through multiple linear locations.

pid_controlled_plant

pid_controlled_plant(kp: float = 6.0, ki: float = 2.0, kd: float = 1.0, stiffness: float = 3.0, damping: float = 0.6, *, u_min: float = -1.0, u_max: float = 1.0, initial_state: ndarray | None = None) -> HybridSystem

Create a PID-controlled second-order plant with saturation.

The state is [position, velocity, integral_error]. Simulate with an explicit finite two-element input vector [reference, reference_rate]; the second component is the reference's time derivative. The controller tracks this reference and saturates the control input, yielding hybrid locations.

Parameters:

Name Type Description Default
kp float

Proportional gain.

6.0
ki float

Integral gain.

2.0
kd float

Derivative gain.

1.0
stiffness float

Plant stiffness.

3.0
damping float

Plant damping.

0.6
u_min float

Minimum control input.

-1.0
u_max float

Maximum control input.

1.0
initial_state ndarray | None

Optional initial state.

None

Returns:

Type Description
HybridSystem

HybridSystem configured for PID control with saturation.

piecewise_affine

piecewise_affine(a_left: ndarray | None = None, a_right: ndarray | None = None, b_left: ndarray | None = None, b_right: ndarray | None = None, threshold: float = 0.0, initial_state: ndarray | None = None) -> HybridSystem

Create a piecewise affine system with oscillatory switching.

Parameters:

Name Type Description Default
a_left ndarray | None

Linear dynamics matrix for the left region.

None
a_right ndarray | None

Linear dynamics matrix for the right region.

None
b_left ndarray | None

Affine offset for the left region.

None
b_right ndarray | None

Affine offset for the right region.

None
threshold float

Event-surface threshold on x[0].

0.0
initial_state ndarray | None

Optional initial state.

None

Returns:

Type Description
HybridSystem

HybridSystem configured with piecewise affine dynamics.

relay_integrator

relay_integrator(rate: float = 1.0, lower: float = -1.0, upper: float = 1.0, initial_state: ndarray | None = None) -> HybridSystem

Create a relay integrator with hysteresis.

Parameters:

Name Type Description Default
rate float

Magnitude of the integrator rate.

1.0
lower float

Lower switching threshold.

-1.0
upper float

Upper switching threshold.

1.0
initial_state ndarray | None

Optional initial scalar state.

None

Returns:

Type Description
HybridSystem

HybridSystem configured with relay dynamics.

switched_linear

switched_linear(a_on: ndarray | None = None, a_off: ndarray | None = None, threshold: float = 0.0, initial_state: ndarray | None = None) -> HybridSystem

Create a switched linear system benchmark.

Parameters:

Name Type Description Default
a_on ndarray | None

Dynamics matrix for the "on" location.

None
a_off ndarray | None

Dynamics matrix for the "off" location.

None
threshold float

Switching threshold on x[0].

0.0
initial_state ndarray | None

Optional initial state.

None

Returns:

Type Description
HybridSystem

HybridSystem configured with switching linear dynamics.

tank_valves

tank_valves(area_1: float = 1.0, area_2: float = 1.2, inflow: float = 0.02, *, outlet_area_1: float = 0.002, outlet_area_2: float = 0.012, valve_gain: float = 0.01, high_level: float = 1.2, low_level: float = 0.4, gravity: float = 9.81, initial_state: Sequence[float] | ndarray | None = None) -> HybridSystem

Create a gravity-drained two-tank benchmark.

Both tanks have atmospheric free surfaces and share a bottom datum. An always-on pump feeds tank 1. A check valve permits gravity-driven transfer from tank 1 to tank 2 only while open; overflow is not modeled.

Parameters:

Name Type Description Default
area_1 float

Tank 1 cross-sectional area in square meters.

1.0
area_2 float

Tank 2 cross-sectional area in square meters.

1.2
inflow float

Constant pump inflow to tank 1 in cubic meters per second.

0.02
outlet_area_1 float

Tank 1 discharge coefficient times physical outlet area, in square meters.

0.002
outlet_area_2 float

Tank 2 discharge coefficient times physical outlet area, in square meters.

0.012
valve_gain float

Effective open-valve transfer area in square meters.

0.01
high_level float

Tank 1 level in meters at which the valve opens.

1.2
low_level float

Tank 1 level in meters at which the valve closes.

0.4
gravity float

Gravitational acceleration in meters per second squared.

9.81
initial_state Sequence[float] | ndarray | None

Initial [level_1, level_2] in meters.

None

Returns:

Type Description
HybridSystem

HybridSystem configured for valve-controlled gravity flow.

References

The gravity-orifice basis follows the standard square-root head model described in https://www.mdpi.com/2076-3417/13/9/5414, Section 2.1.2.

thermostat

thermostat(ambient: float = 20.0, heating_power: float = 5.0, cooling_rate: float = 0.3, hysteresis: float = 2.0, initial_state: ndarray | None = None) -> HybridSystem

Create a thermostat benchmark system.

Simulate with an explicit one-element finite input vector [target] giving the target temperature at each requested time. The target sets the high and low switching thresholds; flows depend only on state.

Parameters:

Name Type Description Default
ambient float

Ambient temperature.

20.0
heating_power float

Heating input strength.

5.0
cooling_rate float

Cooling coefficient.

0.3
hysteresis float

Temperature hysteresis band width.

2.0
initial_state ndarray | None

Optional initial temperature.

None

Returns:

Type Description
HybridSystem

HybridSystem configured for thermostat switching.

time_forced_switch

time_forced_switch(period: float = 1.0, initial_state: ndarray | None = None) -> HybridSystem

Create a system that alternates between fast and slow decay.

The two physical state coordinates are unchanged at switches. Each location is active for half the period.

Parameters:

Name Type Description Default
period float

Full period of the two-location cycle.

1.0
initial_state ndarray | None

Optional initial [x1, x2] state.

None

Returns:

Type Description
HybridSystem

HybridSystem with time-triggered switches.

time_varying_event_surface

time_varying_event_surface(*, hysteresis: float = 0.2, drift: float = 0.6, damping: float = 0.4, initial_state: ndarray | None = None) -> HybridSystem

Create a system with externally driven event-surface thresholds.

Simulate with an explicit finite one-element input vector [threshold]. Both switching surfaces use the supplied threshold at the queried time.

Parameters:

Name Type Description Default
hysteresis float

Event-surface hysteresis width.

0.2
drift float

Drift magnitude per location.

0.6
damping float

Damping on the second state.

0.4
initial_state ndarray | None

Optional initial state.

None

Returns:

Type Description
HybridSystem

HybridSystem with time-dependent event surfaces.

wind_turbine

wind_turbine(*, initial_state: Sequence[float] | ndarray | None = None, pitch_kp: float = 0.01882681, pitch_ki: float = 0.008068634, speed_hysteresis: float = 0.2) -> HybridSystem

Model an already-running 5 MW-class wind turbine.

Wind turns the rotor; the generator resists rotation to extract power. Wind thrust bends the tower, modeled as a damped mass-spring system. Turning the blades out of the wind (pitching) reduces their loading. Startup, shutdown, and emergency-braking control are not modeled.

The state vector is [omega, x, v, beta, beta_rate, integral]:

  • omega: rotor speed (rad/s).
  • x: tower displacement from its undeflected position (m).
  • v: tower velocity (m/s).
  • beta: blade pitch (rad).
  • beta_rate: pitch rate (rad/s).
  • integral: pitch controller's integral contribution (rad).

By default, the rotor turns at 0.65 rad/s and all other states are zero. The five locations set generator torque according to generator speed:

  • no_generation: zero torque; the rotor spins without generating.
  • gradual_generation: torque increases linearly with speed.
  • below_rated_power: torque is proportional to speed squared.
  • approaching_rated_power: a second linear torque ramp.
  • rated_power: constant power, with torque equal to power/speed.

Rated power is about 5.30 MW at the generator shaft; electrical output is not modeled. Locations form a two-way chain in the order above. Upward and downward speed thresholds differ by 2*speed_hysteresis; between them, the current mode is retained. Switching leaves physical states unchanged.

Pitch control runs in every mode, using speed error and its accumulated value (PI control). Blade motion lags the command, limited to 0..18 degrees. The controller corrects accumulated error at those limits; they are not mechanical stops. Hardware and controller constants are recorded in system.parameters.

Supply simulate(..., input_stream=...) with a finite one-element vector [wind] containing a positive wind speed (m/s), such as lambda t: np.array([11.0]). Relative wind is u = wind - v; it and rotor speed must stay positive. With radius R=63 m, the tip-speed ratio omega*R/u must stay in 2.5..14.5 and pitch in -2..20 degrees. Leaving the polynomial fit's domain raises an error.

The fit gives torque coefficient Cq and thrust coefficient Ct. Rotor torque is rho*pi*R**3*u**2*Cq/2 and thrust rho*pi*R**2*u**2*Ct/2, where rho is air density. Negative torque brakes the rotor. The rigid, lossless gearbox makes generator speed 97*omega. Generator torque and inertia are referred to the rotor by factors of 97 and 97 squared. Generator torque has no delay or rate limit. Aerodynamics assume head-on wind and an instantaneous response to current conditions (a quasi-steady approximation); blades are rigid.

Parameters:

Name Type Description Default
initial_state Sequence[float] | ndarray | None

Optional six-element running state (copied).

None
pitch_kp float

Positive finite pitch proportional gain (rad per rad/s).

0.01882681
pitch_ki float

Positive finite integral gain (rad per rad/s per second).

0.008068634
speed_hysteresis float

Positive generator-speed threshold halfwidth (rad/s).

0.2

Returns:

Type Description
HybridSystem

HybridSystem with five torque-control locations.

valve_closure

valve_closure(delay: float = 1.0) -> HybridSystem

Fill a tank until its inlet valve closes after threshold detection.

Water height rises at a constant rate until it reaches the closing threshold, which schedules valve closure. Filling continues during the delay; after closure the height stays constant.

Parameters:

Name Type Description Default
delay float

Finite, nonnegative closing delay in model time units. Use zero for immediate closure at the threshold.

1.0

Returns:

Type Description
HybridSystem

A two-location system with water height as its single state coordinate.

HyDRA identification

Curated HyDRA hybrid-system identification API.

Classes

HyDRACallback

Observe discovery geometry and scalar fits; override any desired hooks.

Hooks run synchronously, and their exceptions propagate to the caller.

Methods:
start
start(*, trace_count: int, threshold: float, start_width: int, step_width: int) -> None

Begin discovery over trace_count independent traces.

threshold is the strict upper bound on absolute prediction error. start_width and step_width are the initial candidate window size and its growth step, measured in consecutive observations.

pending_segment_found
pending_segment_found(segment: TraceSegment) -> None

Report the unassigned segment where candidate fitting will begin.

candidate_window_evaluated
candidate_window_evaluated(*, segment: TraceSegment, fit: float) -> None

Report candidate accuracy on the observations in segment.

fit is the maximum absolute prediction error over that window.

candidate_selected
candidate_selected(*, segment: TraceSegment, fit: float) -> None

Report the last evaluated window whose candidate met the threshold.

fit is its maximum absolute prediction error. The selected candidate will next be used to group matching unassigned observations.

grouping_evaluated
grouping_evaluated(*, flow_id: int, accepted_segments: tuple[TraceSegment, ...], considered_count: int) -> None

Report candidate acceptance across all traces, before final refitting.

Parameters:

Name Type Description Default
flow_id int

ID reserved for this candidate if its final fit succeeds.

required
accepted_segments tuple[TraceSegment, ...]

Previously unassigned observations that match the candidate within the error threshold.

required
considered_count int

Total number of unassigned observations across all traces before grouping, including both accepted and rejected observations. Already assigned observations are excluded.

required
flow_finalized
flow_finalized(*, flow_id: int, flow: LearnedFlow) -> None

Report a flow successfully refitted on its accepted observations.

flow_id is its position in result.flows. flow contains the refitted model and the segments accepted before that refit.

finish
finish(result: LearnedFlows) -> None

Receive the successful result assigning every supplied observation.

result is the same object returned by learn. This hook is not called if identification, a backend, or an earlier callback fails.

HyDRAIdentificationError

HyDRAIdentificationError(segment: TraceSegment, message: str)

Bases: RuntimeError

Discovery could not assign the supplied observations in a segment.

Attributes:

Name Type Description
segment TraceSegment

The failing trace segment, with half-open row bounds.

Attributes
segment instance-attribute
segment: TraceSegment = segment

HyDRALearner

HyDRALearner(regressor_factory: Callable[[], SupervisedLearner], threshold: float, start_width: int = 10, step_width: int = 5, callback: HyDRACallback | None = None)

Discover shared flow models from ordered traces with one scalar target.

HyDRA fits candidates on growing windows of consecutive observations, groups matching observations across traces, and refits each discovered flow on its accepted group. Supply derivatives as targets to learn rates of change, or another regression target to learn its shared behaviors.

Parameters:

Name Type Description Default
regressor_factory Callable[[], SupervisedLearner]

Creates a fresh batch SupervisedLearner for every candidate window and final fit. Each instance receives one learn call with the observations for that fit. Fitting state must be independent: later fits must not mutate previously returned models or any prediction-affecting state. A fresh wrapper around a shared mutable estimator is insufficient; immutable sharing is fine.

required
threshold float

Finite, non-negative maximum absolute prediction error. Candidate acceptance uses the strict comparison error < threshold.

required
start_width int

Positive initial window size in consecutive observations.

10
step_width int

Positive number of observations added at each window step.

5
callback HyDRACallback | None

Observer for discovery progress; defaults to a no-op HyDRACallback.

None
Attributes
regressor_factory instance-attribute
regressor_factory: Callable[[], SupervisedLearner] = regressor_factory
threshold instance-attribute
threshold: float = threshold
start_width instance-attribute
start_width: int = start_width
step_width instance-attribute
step_width: int = step_width
callback instance-attribute
callback: HyDRACallback = callback if callback is not None else HyDRACallback()
Methods:
learn
learn(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_features: Sequence[str]) -> LearnedFlows

Discover flows and return their models and observation membership.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Nonempty collection of nonempty Polars frames in observation order. Each frame is an independent trace. Additional columns can be retained when projecting the result onto these frames.

required
input_features Sequence[str]

Nonempty, unique input column names.

required
output_features Sequence[str]

Exactly one target column, distinct from inputs.

required

Selected columns must contain finite numeric values with no nulls. flow_id is reserved for assignments and must be absent from frames and feature lists. Selected observations and backend predictions use Polars Float64 as their computational representation for fitting and residuals, not as a precision improvement. Decimal values and large integers can collapse to the same Float64 value. Original frames are unchanged.

Returns:

Type Description
LearnedFlows

Models and segments assigning every supplied observation to a flow.

Raises:

Type Description
ValueError

Data or feature roles violate the stated constraints.

TypeError

A supplied trace is not a Polars DataFrame.

HyDRAIdentificationError

A pending segment cannot be identified. Its segment identifies the failing trace and row bounds.

Backend and callback exceptions propagate to the caller.

LearnedFlow dataclass

LearnedFlow(model: Model, segments: tuple[TraceSegment, ...])

A fitted model and the observation segments accepted by its candidate.

Segments use half-open row bounds and are ordered by trace, then row. Membership is recorded before the final model refit.

Attributes
model instance-attribute
model: Model
segments instance-attribute
segments: tuple[TraceSegment, ...]

LearnedFlows dataclass

LearnedFlows(flows: tuple[LearnedFlow, ...], trace_lengths: tuple[int, ...], input_features: tuple[str, ...], output_features: tuple[str, ...])

Discovered flows and their membership in the supplied observations.

Parameters:

Name Type Description Default
flows tuple[LearnedFlow, ...]

Fitted models with accepted segments. A flow's position is its shared flow ID across all traces.

required
trace_lengths tuple[int, ...]

Row counts in the original trace order.

required
input_features tuple[str, ...]

Ordered input column names used for fitting.

required
output_features tuple[str, ...]

Output column names used for fitting.

required

Accepted segments cover every supplied observation exactly once. Use to_labeled_frames(original_traces) to train a selector, or to_flow_ids() for row-aligned NumPy assignments.

Attributes
flows instance-attribute
flows: tuple[LearnedFlow, ...]
trace_lengths instance-attribute
trace_lengths: tuple[int, ...]
input_features instance-attribute
input_features: tuple[str, ...]
output_features instance-attribute
output_features: tuple[str, ...]
Methods:
to_flow_ids
to_flow_ids() -> list[ndarray]

Return fresh, fully populated int64 arrays of row assignments.

to_labeled_frames
to_labeled_frames(traces: Sequence[DataFrame]) -> list[DataFrame]

Add an Int64 flow_id column to each supplied frame.

Frames must match the original trace count, row counts, and positional order. All existing columns are preserved; every row has a flow ID.

TraceSegment dataclass

TraceSegment(trace_index: int, start: int, stop: int)

Consecutive observations within a trace: start inclusive, stop exclusive.

Parameters:

Name Type Description Default
trace_index int

Index in the supplied trace collection.

required
start int

Index of the first included observation.

required
stop int

Index just past the last observation.

required
Attributes
trace_index instance-attribute
trace_index: int
start instance-attribute
start: int
stop instance-attribute
stop: int

HyDRAModel

HyDRAModel(flow_models: list[Model], *, input_features: Sequence[str], output_features: Sequence[str], selector: HybridDecisionTreeModel | None = None, trace_schema: HyDRATraceSchema | None = None)

Bases: Model

Model composed of learned flow models and an optional selector.

A single-flow model predicts directly with that flow model. A multi-flow model needs a selector for batch prediction. Supply a trace schema for derivative rollout. Persistence uses pickle: load models only from trusted sources.

Attributes
flow_models instance-attribute
flow_models = flow_models
input_features instance-attribute
input_features = list(input_features)
output_features instance-attribute
output_features = list(output_features)
selector instance-attribute
selector = selector
trace_schema instance-attribute
trace_schema = trace_schema
Methods:
predict_with_diagnostics
predict_with_diagnostics(input_features: DataFrame | LazyFrame) -> HyDRABatchPrediction

Route observations to flow models and retain row alignment details.

Parameters:

Name Type Description Default
input_features DataFrame | LazyFrame

Frame containing all flow inputs and selector features. Flow inputs are converted to Float64 for prediction.

required

Returns:

Type Description
HyDRABatchPrediction

Predicted outputs, original row indices, and selector decisions.

HyDRABatchPrediction

History-based selectors begin after their required warmup rows.

HyDRABatchPrediction

Selectors using previous-flow features require the stateful

HyDRABatchPrediction

selector runtime.

simulate
simulate(t_span: tuple[float, float], x0: Iterable[float], *, input_stream: InputStream | None = None, include_inputs: bool = False, sample_times: Iterable[float] | None = None, sample_dt: float | None = None, rtol: float = 1e-07, atol: float = 1e-09, max_step: float | None = None) -> DataFrame

Integrate a learned derivative model on the requested time grid.

Requires a trace schema with matching state and derivative widths, and an input stream for any external inputs. Multi-flow models require a selector using current time, state, and external input features.

Select a flow at each grid point, including the final endpoint, and integrate it until the next point. The chosen grid therefore determines when flow changes can occur.

Returns:

Type Description
DataFrame

Sampled frame with t, state columns x0, x1, etc.,

DataFrame

flow_id for the selected model, and flow_time for the time

DataFrame

since it became active. With include_inputs=True, also includes

DataFrame

external input columns u0, u1, etc.

predict_next_state
predict_next_state(state: Iterable[float], *, t: float, dt: float, input_stream: InputStream | None = None, rtol: float = 1e-07, atol: float = 1e-09, max_step: float | None = None) -> ndarray

PlotCallback

PlotCallback(frame: DataFrame, *, columns: Sequence[str], time_column: str | None = None, trace_index: int = 0, ax: Axes | None = None, show: bool = True, pause: float = 0.001)

Bases: HyDRACallback

Plot one selected trace and its discovery segments.

Finalized flows use plain bands; provisional grouping uses hatching and the active window uses a dotted outline. Singleton rows remain visible as markers. Flow colors belong to this identification result, not to native locations or another fit. Only overlays owned by this callback are removed.

show=True enables live display and GUI event processing, with pause controlling the update interval. show=False only updates artists: no redraw, show, pause, or GUI event-loop calls are made. The caller owns drawing or saving the resulting canvas and closing the figure.

Attributes
columns instance-attribute
columns = list(columns)
frame instance-attribute
frame = frame
time_column instance-attribute
time_column = time_column
trace_index instance-attribute
trace_index = trace_index
ax instance-attribute
ax = ax
show instance-attribute
show = show
pause instance-attribute
pause = pause
Methods:
start
start(*, trace_count: int, threshold: float, start_width: int, step_width: int) -> None
pending_segment_found
pending_segment_found(segment: TraceSegment) -> None
candidate_window_evaluated
candidate_window_evaluated(*, segment: TraceSegment, fit: float) -> None
candidate_selected
candidate_selected(*, segment: TraceSegment, fit: float) -> None
grouping_evaluated
grouping_evaluated(*, flow_id: int, accepted_segments: tuple[TraceSegment, ...], considered_count: int) -> None
flow_finalized
flow_finalized(*, flow_id: int, flow: LearnedFlow) -> None
finish
finish(result: LearnedFlows) -> None

HyDRATraceSchema dataclass

HyDRATraceSchema(time: str, state: tuple[str, ...], derivative: tuple[str, ...], inputs: tuple[str, ...] = ())

Column roles for a learned derivative model and its rollout.

Parameters:

Name Type Description Default
time str

Time column name.

required
state tuple[str, ...]

State column names used as model inputs.

required
derivative tuple[str, ...]

Derivative column names used as model outputs, in the same order as their corresponding state columns.

required
inputs tuple[str, ...]

Optional external input column names.

()

All column names must be distinct. input_features collects the time, state, and external input columns. Rollout requires equal numbers of state and derivative columns.

Attributes
time instance-attribute
time: str
state instance-attribute
state: tuple[str, ...]
derivative instance-attribute
derivative: tuple[str, ...]
inputs class-attribute instance-attribute
inputs: tuple[str, ...] = ()
input_features property
input_features: tuple[str, ...]
Methods:
validate_input_features
validate_input_features(input_features: Sequence[str]) -> None
validate_output_features
validate_output_features(output_features: Sequence[str]) -> None
validate_state_derivative_width
validate_state_derivative_width() -> None

FlowPredictionResult dataclass

FlowPredictionResult(ready: bool, flow_id: int | None, probabilities: dict[int, float] = dict(), leaf_id: int | None = None, flow_model: Model | None = None)
Attributes
ready instance-attribute
ready: bool
flow_id instance-attribute
flow_id: int | None
probabilities class-attribute instance-attribute
probabilities: dict[int, float] = field(default_factory=dict)
leaf_id class-attribute instance-attribute
leaf_id: int | None = None
flow_model class-attribute instance-attribute
flow_model: Model | None = None

HybridDecisionTreeLearner

HybridDecisionTreeLearner(feature_config: SelectorFeatureConfig, **tree_kwargs: Any)

Bases: SupervisedLearner

Attributes
classifier instance-attribute
classifier: DecisionTreeClassifier = DecisionTreeClassifier(**classifier_kwargs)
feature_config instance-attribute
feature_config = feature_config
Methods:
learn
learn(inputs: DataFrame | LazyFrame, outputs: DataFrame | LazyFrame) -> HybridDecisionTreeModel
learn_from_traces
learn_from_traces(traces: list[DataFrame], flow_models_by_id: dict[int, Model] | None = None) -> HybridDecisionTreeModel

HybridDecisionTreeModel

HybridDecisionTreeModel(classifier: DecisionTreeClassifier, feature_columns: tuple[str, ...], feature_config: SelectorFeatureConfig, flow_models_by_id: dict[int, Model] | None = None)

Bases: Model

Attributes
classifier instance-attribute
classifier = classifier
feature_columns instance-attribute
feature_columns = feature_columns
feature_config instance-attribute
feature_config = feature_config
flow_models_by_id instance-attribute
flow_models_by_id = flow_models_by_id or {}
Methods:
predict_details
predict_details(input_features: DataFrame | LazyFrame) -> list[FlowPredictionResult]
resolve_flow
resolve_flow(flow_id: int) -> Model | None
feature_importances
feature_importances() -> dict[str, float]
tree_text
tree_text() -> str
summary_text
summary_text() -> str
leaf_summary_text
leaf_summary_text() -> str
flow_summary_text
flow_summary_text() -> str
to_dot
to_dot() -> str
to_svg
to_svg() -> str
save_svg
save_svg(path: str | Path) -> None
debug_prediction_text
debug_prediction_text(input_features: DataFrame | LazyFrame) -> str
inspect
inspect() -> SelectorInspection

SelectorEvaluationReport dataclass

SelectorEvaluationReport(accuracy: float, samples: int, confusion_matrix: DataFrame)
Attributes
accuracy instance-attribute
accuracy: float
samples instance-attribute
samples: int
confusion_matrix instance-attribute
confusion_matrix: DataFrame

SelectorFeatureConfig dataclass

SelectorFeatureConfig(state_features: tuple[str, ...] = (), input_features: tuple[str, ...] = (), derivative_features: tuple[str, ...] = (), state_history: int = 0, input_history: int = 0, derivative_history: int = 0, flow_history: int = 0)

Configuration for selector feature construction.

Parameters:

Name Type Description Default
state_features tuple[str, ...]

State columns available to the selector.

()
input_features tuple[str, ...]

Raw input columns available to the selector.

()
derivative_features tuple[str, ...]

Derivative columns available to the selector.

()
state_history int

Number of previous state rows used for selector features.

0
input_history int

Number of previous input rows used for selector features.

0
derivative_history int

Number of previous derivative rows used for selector features.

0
flow_history int

Number of previous flow IDs used for selector features.

0
Attributes
state_features class-attribute instance-attribute
state_features: tuple[str, ...] = ()
input_features class-attribute instance-attribute
input_features: tuple[str, ...] = ()
derivative_features class-attribute instance-attribute
derivative_features: tuple[str, ...] = ()
state_history class-attribute instance-attribute
state_history: int = 0
input_history class-attribute instance-attribute
input_history: int = 0
derivative_history class-attribute instance-attribute
derivative_history: int = 0
flow_history class-attribute instance-attribute
flow_history: int = 0
max_history property
max_history: int
Methods:
required_columns
required_columns() -> tuple[str, ...]
validate
validate() -> None

SelectorFlowInspection dataclass

SelectorFlowInspection(flow_id: int, weighted_support: float, flow_summary: str)
Attributes
flow_id instance-attribute
flow_id: int
weighted_support instance-attribute
weighted_support: float
flow_summary instance-attribute
flow_summary: str

SelectorInspection dataclass

SelectorInspection(feature_columns: tuple[str, ...], classes: tuple[int, ...], max_depth: int, n_leaves: int, nodes: tuple[SelectorNodeInspection, ...], leaves: tuple[SelectorLeafInspection, ...], flows: tuple[SelectorFlowInspection, ...])
Attributes
feature_columns instance-attribute
feature_columns: tuple[str, ...]
classes instance-attribute
classes: tuple[int, ...]
max_depth instance-attribute
max_depth: int
n_leaves instance-attribute
n_leaves: int
nodes instance-attribute
nodes: tuple[SelectorNodeInspection, ...]
leaves instance-attribute
leaves: tuple[SelectorLeafInspection, ...]
flows instance-attribute
flows: tuple[SelectorFlowInspection, ...]

SelectorLeafInspection dataclass

SelectorLeafInspection(node_id: int, flow_id: int, sample_count: int, weighted_class_support: dict[int, float], flow_summary: str)
Attributes
node_id instance-attribute
node_id: int
flow_id instance-attribute
flow_id: int
sample_count instance-attribute
sample_count: int
weighted_class_support instance-attribute
weighted_class_support: dict[int, float]
flow_summary instance-attribute
flow_summary: str

SelectorNodeInspection dataclass

SelectorNodeInspection(node_id: int, sample_count: int, impurity: float, is_leaf: bool, predicted_flow_id: int, weighted_class_support: dict[int, float], feature_index: int | None = None, feature_name: str | None = None, threshold: float | None = None, left_child_id: int | None = None, right_child_id: int | None = None)
Attributes
node_id instance-attribute
node_id: int
sample_count instance-attribute
sample_count: int
impurity instance-attribute
impurity: float
is_leaf instance-attribute
is_leaf: bool
predicted_flow_id instance-attribute
predicted_flow_id: int
weighted_class_support instance-attribute
weighted_class_support: dict[int, float]
feature_index class-attribute instance-attribute
feature_index: int | None = None
feature_name class-attribute instance-attribute
feature_name: str | None = None
threshold class-attribute instance-attribute
threshold: float | None = None
left_child_id class-attribute instance-attribute
left_child_id: int | None = None
right_child_id class-attribute instance-attribute
right_child_id: int | None = None

StatefulHybridDecisionTreeSelector

StatefulHybridDecisionTreeSelector(model: HybridDecisionTreeModel, seed_flows: Sequence[int] = ())
Attributes
model instance-attribute
model = model
config instance-attribute
config = model.feature_config
Methods:
predict
predict(sample: Mapping[str, Any]) -> FlowPredictionResult

Functions:

evaluate_selector_autoregressive

evaluate_selector_autoregressive(model: HybridDecisionTreeModel, traces: list[DataFrame], seed_flows: tuple[int, ...] | list[int] = ()) -> SelectorEvaluationReport

evaluate_selector_oracle

evaluate_selector_oracle(model: HybridDecisionTreeModel, traces: list[DataFrame]) -> SelectorEvaluationReport

Dainarx identification

Noise-free sampled NARX hybrid-system identification.

Type Aliases

NonlinearTerm

NonlinearTerm = Callable[[FloatArray, FloatArray], ArrayLike]

A nonlinear regression feature called as term(history, current_input).

history has shape (order, n_outputs), oldest observation first; current_input has shape (n_inputs,) and is aligned with the output being predicted. Here n_outputs and n_inputs count the selected output and input features. Columns follow their selected feature order. Return a (n_outputs,) vector in output-feature order, with one feature value for each output's regression.

Classes

NarxModel dataclass

NarxModel(order: int, input_features: tuple[str, ...], output_features: tuple[str, ...], time_column: str, dt: float, nonlinear_terms: tuple[NonlinearTerm, ...], coefficients: FloatArray, rmse: float)

A nonlinear autoregressive model with exogenous inputs (NARX).

Predict the next output from previous outputs and the input at the row being predicted. Let n_outputs and n_inputs count the selected output and input features; n_coefficients = len(nonlinear_terms) + order * n_outputs + n_inputs + 1.

Attributes:

Name Type Description
order int

Number of previous output observations used for prediction.

input_features tuple[str, ...]

Selected input names, in input-vector order.

output_features tuple[str, ...]

Selected output names, in output-vector order.

time_column str

Timestamp column name.

dt float

Sampling period in timestamp units.

nonlinear_terms tuple[NonlinearTerm, ...]

Ordered nonlinear regression features; see NonlinearTerm.

coefficients FloatArray

Array of shape (n_outputs, n_coefficients). Columns are nonlinear terms, all outputs at lag 1 through order (newest lag first), current inputs, then bias.

rmse float

Largest root-mean-square error (RMSE) among the outputs, measured on training targets in output units. This is not a guarantee of future prediction error.

Attributes
order instance-attribute
order: int
input_features instance-attribute
input_features: tuple[str, ...]
output_features instance-attribute
output_features: tuple[str, ...]
time_column instance-attribute
time_column: str
dt instance-attribute
dt: float
nonlinear_terms instance-attribute
nonlinear_terms: tuple[NonlinearTerm, ...]
coefficients instance-attribute
coefficients: FloatArray
rmse instance-attribute
rmse: float
Methods:
predict
predict(history: ArrayLike, current_input: ArrayLike) -> FloatArray

Predict the next output from output history and its aligned input.

n_outputs and n_inputs count the selected output and input features, respectively.

Parameters:

Name Type Description Default
history ArrayLike

Output history of shape (order, n_outputs), oldest observation first, with columns in output_features order.

required
current_input ArrayLike

A (n_inputs,) vector in input_features order, aligned with the output being predicted, not lagged.

required

Returns:

Type Description
FloatArray

A (n_outputs,) vector in output_features order.

DainarxDynamics dataclass

DainarxDynamics(locations: tuple[NarxModel, ...], segments: tuple[DainarxSegment, ...], trace_lengths: tuple[int, ...], eta: float)

Complete dynamics discovery and original-observation membership.

This is not an executable hybrid model: guards and post-switch predictors still require transition fitting, which can fail independently. Membership is discovered, not ground truth or a per-row RMSE certificate. The first order rows of each segment are labeled even though ordinary location fitting excludes them. Adjacent segments may share a location ID; retain their detected boundaries even when projected labels do not change.

Attributes:

Name Type Description
locations tuple[NarxModel, ...]

Ordinary NARX models indexed by contiguous location IDs. All share history order, selected feature order, time column, sampling period, and ordered nonlinear feature functions.

segments tuple[DainarxSegment, ...]

Half-open original-frame intervals [start, stop), ordered by independent trace and row, covering every observation once.

trace_lengths tuple[int, ...]

Original observation counts in input trace order.

eta float

Nonnegative maximum per-output training RMSE threshold, in output units, not a rollout error bound.

Attributes
locations instance-attribute
locations: tuple[NarxModel, ...]
segments instance-attribute
segments: tuple[DainarxSegment, ...]
trace_lengths instance-attribute
trace_lengths: tuple[int, ...]
eta instance-attribute
eta: float
Methods:
to_location_ids
to_location_ids() -> list[ndarray]

Project discovered membership onto all original observation rows.

Returns:

Type Description
list[ndarray]

Fresh, fully populated int64 arrays of shape (trace_length,), one per independent trace in original order. Segment warmup rows are included; equal adjacent labels do not erase the boundaries retained in segments.

to_labeled_frames
to_labeled_frames(traces: Sequence[DataFrame]) -> list[DataFrame]

Add discovered location_id membership to original observations.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Original Polars frames in discovery order. Keep the original row correspondence; observation identity is not verified by the trace-length checks.

required

Returns:

Type Description
list[DataFrame]

Independent frames preserving every original column, column order, and dtype, followed by a non-null Int64 location_id column. Labels describe membership, including segment warmup rows, not ground truth or a final-fit error certificate.

DainarxFit dataclass

DainarxFit(model: DainarxModel, segments: tuple[DainarxSegment, ...], eta: float)

The executable model, complete segmentation, and acceptance threshold.

Attributes:

Name Type Description
model DainarxModel

Learned locations, guards, and post-switch predictors.

segments tuple[DainarxSegment, ...]

Half-open original-frame intervals [start, stop), ordered by trace and row, covering all input observations.

eta float

Fitting acceptance threshold in output units, not a rollout error bound.

Attributes
model instance-attribute
model: DainarxModel
segments instance-attribute
segments: tuple[DainarxSegment, ...]
eta instance-attribute
eta: float

DainarxSegment dataclass

DainarxSegment(trace_index: int, start: int, stop: int, location_id: int)

A half-open sampled-trace interval and its learned location ID.

Attributes:

Name Type Description
trace_index int

Index into the input trace collection.

start int

Inclusive row index in the original trace frame.

stop int

Exclusive row index in the original trace frame.

location_id int

Learned location ID shared across traces, indexing DainarxDynamics.locations or DainarxModel.locations.

Attributes
trace_index instance-attribute
trace_index: int
start instance-attribute
start: int
stop instance-attribute
stop: int
location_id instance-attribute
location_id: int

DainarxModel dataclass

DainarxModel(locations: tuple[NarxModel, ...], transitions: tuple[DainarxTransition, ...])

Learned sampled dynamics and transitions between location IDs.

Attributes:

Name Type Description
locations tuple[NarxModel, ...]

NARX models indexed by contiguous location IDs 0 through len(locations) - 1. Models share selected features, output-history order, and sampling period.

transitions tuple[DainarxTransition, ...]

Guarded edges whose source and destination are IDs indexing locations.

Attributes
locations instance-attribute
locations: tuple[NarxModel, ...]
transitions instance-attribute
transitions: tuple[DainarxTransition, ...]
Methods:
rollout
rollout(inputs: DataFrame, *, output_history: ArrayLike, initial_location: int) -> DataFrame

Recursively predict outputs from exactly order seed rows.

At row t, guards inspect output[t] and input[t]; a switch affects row t + 1. Its predictor uses the preceding output history and input[t + 1]. Post-switch predictors run in tuple order before destination dynamics resume. Multiple enabled guards or an interrupted reset sequence raise ValueError; no priority is invented.

Parameters:

Name Type Description Default
inputs DataFrame

Polars frame with known timestamps and selected inputs for all rows, including seeds, at the models' shared sampling period. Unrelated columns are ignored. Exactly order rows are allowed for a seed-only return.

required
output_history ArrayLike

Seed array of shape (order, n_outputs), oldest row first, in selected output-feature order. n_outputs is the number of selected outputs.

required
initial_location int

Learned location ID at the last seed row, which must not be inside an unfinished reset sequence.

required

Returns:

Type Description
DataFrame

A Polars frame with the timestamp column, selected output columns, and location_id, including all seeds. Earlier seed IDs are null; the last seed ID is initial_location.

DainarxTransition dataclass

DainarxTransition(source: int, destination: int, guard: SVC, reset_predictors: tuple[NarxModel, ...])

An output-and-input SVM guard and ordered post-switch predictors.

Attributes:

Name Type Description
source int

Source location ID, indexing DainarxModel.locations.

destination int

Destination location ID, indexing DainarxModel.locations.

guard SVC

Fitted SVM whose feature vector is selected outputs followed by selected inputs, in their feature order. True enables this edge from its source location.

reset_predictors tuple[NarxModel, ...]

Predictors for the first order post-switch outputs, applied in tuple order before destination dynamics. These are not instantaneous physical reset maps.

Attributes
source instance-attribute
source: int
destination instance-attribute
destination: int
guard instance-attribute
guard: SVC
reset_predictors instance-attribute
reset_predictors: tuple[NarxModel, ...]

Functions:

fit_narx

fit_narx(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_features: Sequence[str], time_column: str, order: int, nonlinear_terms: Sequence[NonlinearTerm] = ()) -> NarxModel

Fit one NARX model to predict the next output from history and inputs.

Use ordinary least squares without crossing trace histories. Each trace needs more than order observations; pooled targets must number at least n_coefficients = len(nonlinear_terms) + order * n_outputs + n_inputs + 1, where n_outputs and n_inputs count selected features. Rank-deficient fits are allowed.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent Polars frames sampled at the same interval. Start times and lengths may differ; only selected columns are used.

required
input_features Sequence[str]

Input column names, in input-vector order.

required
output_features Sequence[str]

Output column names, in output-vector order.

required
time_column str

Timestamp column, increasing within each trace.

required
order int

Number of previous output observations used for prediction.

required
nonlinear_terms Sequence[NonlinearTerm]

Ordered nonlinear regression features following the NonlinearTerm contract.

()

Returns:

Type Description
NarxModel

A NarxModel with fitted coefficients and maximum per-output training RMSE.

fit_dainarx

fit_dainarx(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_features: Sequence[str], time_column: str, order: int, window: int, guard_kernel: Literal['linear', 'poly', 'rbf', 'sigmoid'], guard_c: float, guard_gamma: Literal['scale', 'auto'] | float, nonlinear_terms: Sequence[NonlinearTerm] = (), eta: float | None = None, guard_degree: int = 3, guard_coef0: float = 0.0) -> DainarxFit

Infer sampled NARX locations, SVM guards, and post-switch predictors.

Compose fit_dainarx_dynamics and fit_dainarx_transitions into a complete executable model. Use those stages separately to inspect dynamics and annotate observations even when transition fitting later fails.

Fitting acceptance uses the largest per-output root-mean-square error (RMSE), rather than the paper's unnormalized L2 residual. By default, the threshold is scaled from the shared sampling period and the largest absolute selected training output across all traces. See the Dainarx guide for the noise-free method and usage.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent Polars frames sampled at the same interval. Start times and lengths may differ; only selected columns are used.

required
input_features Sequence[str]

Input column names, in input-vector order.

required
output_features Sequence[str]

Output column names, in output-vector order.

required
time_column str

Timestamp column, increasing within each trace.

required
order int

Number of previous output observations used for prediction.

required
window int

Number of observations per screening window, not regression targets. Each trace needs at least this many observations, and window - order must exceed the per-output coefficient count.

required
guard_kernel Literal['linear', 'poly', 'rbf', 'sigmoid']

SVM kernel: "linear", "poly", "rbf", or "sigmoid".

required
guard_c float

Positive inverse regularization strength; larger values penalize training misclassification more.

required
guard_gamma Literal['scale', 'auto'] | float

Nonnegative scale coefficient for "rbf", "poly", and "sigmoid"; ignored for "linear". For RBF, larger values narrow each training observation's influence. "scale" chooses a coefficient from the guard feature count and training-data variance; "auto" uses the reciprocal of the feature count. Guard features are selected outputs followed by selected inputs.

required
nonlinear_terms Sequence[NonlinearTerm]

Ordered nonlinear regression features following the NonlinearTerm contract. Linear terms already include all outputs at every lag, current inputs, and bias.

()
eta float | None

Nonnegative acceptance threshold in output units for the maximum per-output training RMSE. Applies to screening, clustering, location fits, and reset fits; it is not a rollout error guarantee. None uses the automatic threshold scaled from the sampling period and training output magnitude; zero is accepted explicitly.

None
guard_degree int

Nonnegative polynomial degree, used only for "poly".

3
guard_coef0 float

Additive constant in the "poly" and "sigmoid" kernels; ignored for other kernels.

0.0

Returns:

Type Description
DainarxFit

The learned model, complete segmentation, and fitting threshold in a DainarxFit.

Whole segments need more targets than the per-output coefficient count, len(nonlinear_terms) + order * len(output_features) + len(input_features) + 1. Each reset phase needs at least that many transition occurrences. Insufficient or rejected segment/reset/guard evidence, or a detected same-location boundary within a trace, raises ValueError rather than returning a partial model. Rank deficiency alone is allowed.

fit_dainarx_dynamics

fit_dainarx_dynamics(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_features: Sequence[str], time_column: str, order: int, window: int, nonlinear_terms: Sequence[NonlinearTerm] = (), eta: float | None = None) -> DainarxDynamics

Discover ordinary NARX locations and complete observation membership.

Screen segments, cluster pooled regression data, and fit ordinary location models using maximum per-output training RMSE acceptance. Each segment's first order rows are excluded from ordinary fitting, but included in annotations. Same-location boundaries are retained, not coalesced or rejected here: transition fitting determines whether resets are supported.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent Polars frames sampled at the same interval. Start times and lengths may differ; only selected columns are used.

required
input_features Sequence[str]

Input column names, in input-vector order.

required
output_features Sequence[str]

Output column names, in output-vector order.

required
time_column str

Timestamp column, increasing within each trace.

required
order int

Number of preceding output observations used for prediction.

required
window int

Screening window observation count. Each trace needs at least this many rows; window - order must exceed the per-output coefficient count, len(nonlinear_terms) + order * len(output_features) + len(input_features) + 1. Whole segments also need more targets than that count; rank deficiency is allowed.

required
nonlinear_terms Sequence[NonlinearTerm]

Ordered regression feature functions following the NonlinearTerm contract.

()
eta float | None

Nonnegative maximum per-output training RMSE threshold in output units. None uses the automatic threshold scaled from the sampling period and largest absolute selected training output across traces; explicit zero is honored. Applies to screening, clustering, and final location fitting, not rollout.

None

Returns:

Type Description
DainarxDynamics

A DainarxDynamics result containing location predictors and original-trace assignments.

fit_dainarx_transitions

fit_dainarx_transitions(traces: Sequence[DataFrame], dynamics: DainarxDynamics, *, guard_kernel: Literal['linear', 'poly', 'rbf', 'sigmoid'], guard_c: float, guard_gamma: Literal['scale', 'auto'] | float, guard_degree: int = 3, guard_coef0: float = 0.0) -> tuple[DainarxTransition, ...]

Learn guards and post-switch predictors without repeating discovery.

Guards use current selected outputs followed by current selected inputs. Each source-conditioned binary target asks whether the next observation belongs to the edge's destination. Reset predictors use preceding output history and the input aligned with the target row, one per post-switch phase. They share ordinary fitting's coefficient-count and RMSE gates.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Original observation frames, in the same trace and row order used for discovery. Matching lengths alone do not establish that the observations are unchanged.

required
dynamics DainarxDynamics

Complete discovery result supplying locations, segment provenance, trace lengths, feature template, and acceptance eta.

required
guard_kernel Literal['linear', 'poly', 'rbf', 'sigmoid']

SVM kernel: "linear", "poly", "rbf", or "sigmoid".

required
guard_c float

Positive inverse regularization strength.

required
guard_gamma Literal['scale', 'auto'] | float

Nonnegative kernel scale coefficient or "scale" or "auto"; ignored for the linear kernel.

required
guard_degree int

Nonnegative polynomial degree, used only for "poly".

3
guard_coef0 float

Additive constant for "poly" and "sigmoid" kernels.

0.0

Returns:

Type Description
tuple[DainarxTransition, ...]

Learned transitions, ready for DainarxModel(dynamics.locations, transitions).

Raises:

Type Description
ValueError

Transition evidence is insufficient or rejected, or a detected boundary has the same location on both sides.

FaMoS-DTW identification

FaMoS-DTW discovery, affine sampled dynamics, and next-location trees.

Classes

FamosDiscovery dataclass

FamosDiscovery(segments: tuple[FamosSegment, ...], trace_lengths: tuple[int, ...], input_features: tuple[str, ...], output_features: tuple[str, ...], time_column: str, dt: float, input_scales: FloatArray, output_scales: FloatArray, location_keys: tuple[tuple[int, ...], ...])

Discovery independent of dynamics or transition fitting.

Attributes:

Name Type Description
segments tuple[FamosSegment, ...]

Intervals in trace/row order. Boundaries remain distinct even when adjacent intervals have the same location.

trace_lengths tuple[int, ...]

Original training-frame lengths, including warmup.

input_features tuple[str, ...]

Input column names in vector order.

output_features tuple[str, ...]

Output column names in vector order.

time_column str

Timestamp column name.

dt float

Shared sampling period.

input_scales FloatArray

Training maximum absolute inputs, with zero replaced by one. Prediction uses these scales without clipping.

output_scales FloatArray

Corresponding training maximum absolute outputs. This follows the artifact's evaluation entry point; its trace entry point instead uses max-positive output scaling.

location_keys tuple[tuple[int, ...], ...]

Global IDs index tuples of per-signal cluster IDs, ordered as outputs followed by inputs.

Attributes
segments instance-attribute
segments: tuple[FamosSegment, ...]
trace_lengths instance-attribute
trace_lengths: tuple[int, ...]
input_features instance-attribute
input_features: tuple[str, ...]
output_features instance-attribute
output_features: tuple[str, ...]
time_column instance-attribute
time_column: str
dt instance-attribute
dt: float
input_scales instance-attribute
input_scales: FloatArray
output_scales instance-attribute
output_scales: FloatArray
location_keys instance-attribute
location_keys: tuple[tuple[int, ...], ...]
Methods:
to_labeled_frames
to_labeled_frames(traces: Sequence[DataFrame]) -> list[DataFrame]

Append nullable location_id to the original training frames.

Supply the same observations, in the same trace and row order used for discovery. This is a provenance projection, not a classifier for new traces. Frames are not resampled or truncated. An existing location_id column raises ValueError rather than being overwritten.

FamosSegment dataclass

FamosSegment(trace_index: int, start: int, stop: int, location_id: int | None)

One original-frame half-open interval, including unassigned evidence.

Attributes:

Name Type Description
trace_index int

Index in the independent training-trace sequence.

start int

First original row, inclusive.

stop int

Last original row, exclusive.

location_id int | None

Global location ID, or None for derivative warmup or a segment too short for trimmed DTW comparison.

Attributes
trace_index instance-attribute
trace_index: int
start instance-attribute
start: int
stop instance-attribute
stop: int
location_id instance-attribute
location_id: int | None

FamosDynamics dataclass

FamosDynamics(discovery: FamosDiscovery, order: int, coefficients: tuple[FloatArray, ...], sample_counts: tuple[int, ...], ranks: tuple[int, ...], rmse: tuple[FloatArray, ...])

Output-only affine ARX models with exact output-history shifting.

This intentionally fits only the next output, not the artifact's freely fitted augmented lag-state equations. Derivatives are not targets.

Attributes:

Name Type Description
discovery FamosDiscovery

Independent discovery artifact providing feature order, sampling period, training scales, and location IDs.

order int

Number of output-history rows, not derivative order.

coefficients tuple[FloatArray, ...]

One array per location of shape (order * n_outputs + n_inputs + 1, n_outputs). Rows are normalized outputs newest to oldest, source-row inputs, bias.

sample_counts tuple[int, ...]

Number of within-segment regression targets per location.

ranks tuple[int, ...]

Least-squares design ranks. Rank deficiency is reported, not rejected, and does not establish parameter identifiability.

rmse tuple[FloatArray, ...]

Per-location arrays of per-output training RMSE in physical units, not an accuracy acceptance criterion.

Attributes
discovery instance-attribute
discovery: FamosDiscovery
order instance-attribute
order: int
coefficients instance-attribute
coefficients: tuple[FloatArray, ...]
sample_counts instance-attribute
sample_counts: tuple[int, ...]
ranks instance-attribute
ranks: tuple[int, ...]
rmse instance-attribute
rmse: tuple[FloatArray, ...]
Methods:
predict
predict(location_id: int, output_history: ArrayLike, inputs: ArrayLike) -> FloatArray

Predict y[t+1] using q[t], history through t, and u[t].

Supply physical-unit output_history of shape (order, n_outputs), oldest first, and inputs of shape (n_inputs,). Column order is defined by discovery. Return a physical-unit (n_outputs,) vector. Stored training scales are used without clipping.

FamosModel dataclass

FamosModel(dynamics: FamosDynamics, selector: FamosTransitionSelector)

Compose sampled dynamics and next-location selection.

Feature order and location count must match. The caller is responsible for matching location meanings and source-time training alignment; structural checks cannot certify provenance. Each artifact uses its own training scales and exchanges physical-unit values, so scales may differ.

Attributes:

Name Type Description
dynamics FamosDynamics

Affine output-history dynamics.

selector FamosTransitionSelector

Source-conditioned next-location trees with matching output/input feature order and location count.

Attributes
dynamics instance-attribute
dynamics: FamosDynamics
selector instance-attribute
Methods:
rollout
rollout(inputs: DataFrame, *, output_history: ArrayLike, initial_location: int) -> DataFrame

Predict recursively using old-location dynamics before advancing.

Compute BOTH y[t+1] and q[t+1] from q[t], y through t, and u[t]. A row's location governs the interval beginning at that row. This source-input convention intentionally corrects the artifact's use of target-time inputs during rollout despite source-time fitting.

Parameters:

Name Type Description Default
inputs DataFrame

N timestamp/input rows at discovery.dt; unrelated columns are ignored. Return N rows, including seeds. The final input is unused. N must be at least order; seed-only is supported.

required
output_history ArrayLike

Exactly (order, n_outputs) physical-unit seed outputs, oldest first. No later measured outputs are used.

required
initial_location int

Learned ID at the LAST seed row. Earlier seed location IDs are null. Initialization is the caller's choice, not inferred by clustering a held-out trajectory.

required

Returns:

Type Description
DataFrame

Timestamp, output columns and nullable location_id. The last

DataFrame

predicted location is included even without a following output.

FamosTransitionSelector dataclass

FamosTransitionSelector(output_features: tuple[str, ...], input_features: tuple[str, ...], n_locations: int, output_scales: FloatArray, input_scales: FloatArray, trees: tuple[DecisionTreeClassifier, ...])

One inspectable next-location tree per categorical source location.

Explicit source dispatch differs from the artifact's single joint categorical tree; numeric location IDs are never ordered input features.

Attributes:

Name Type Description
output_features tuple[str, ...]

Current observed physical outputs in vector order.

input_features tuple[str, ...]

Current exogenous controls in vector order.

n_locations int

Declared number of categorical locations.

output_scales FloatArray

Training maximum absolute outputs, zero replaced by one.

input_scales FloatArray

Corresponding training maximum absolute inputs.

trees tuple[DecisionTreeClassifier, ...]

Source-indexed fitted sklearn trees. Features are normalized current outputs followed by current inputs; targets are next-row location IDs, including self transitions.

Attributes
output_features instance-attribute
output_features: tuple[str, ...]
input_features instance-attribute
input_features: tuple[str, ...]
n_locations instance-attribute
n_locations: int
output_scales instance-attribute
output_scales: FloatArray
input_scales instance-attribute
input_scales: FloatArray
trees instance-attribute
trees: tuple[DecisionTreeClassifier, ...]
Methods:
predict
predict(current_location: int, outputs: ArrayLike, inputs: ArrayLike) -> int

Return q[t+1] from q[t], physical-unit y[t], and u[t].

Output and input vectors have shapes (n_outputs,) and (n_inputs,), in stored feature order. No future observations or target-time inputs are used. Training scales are not refitted or clipped for prediction.

Functions:

discover_famos_locations

discover_famos_locations(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_features: Sequence[str], window: int, trim: int, peak_height: float, rho_min: float, rho_max: float, time_column: str = 't', max_derivative: int = 1, clustering_derivative: int = 1, alpha: float = 2.0) -> FamosDiscovery

Discover sampled locations without fitting any dynamics.

Each signal is segmented geometrically, then clustered by exact DTW. Global locations are tuples of per-signal clusters, not connected components of a distance graph. Ordered asymmetric reassignment follows trace/row order. The DTW backend is dtaidistance's exact C implementation; MATLAB optimal-path tie equivalence is not claimed.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent, uniformly sampled eager frames. Only selected columns are read; differences and histories restart per trace.

required
input_features Sequence[str]

Inputs used raw for detection and clustering.

required
output_features Sequence[str]

Outputs used raw and through max_derivative for recursive detection, and at clustering_derivative for DTW.

required
window int

Rows on EACH side of a candidate, excluding its center. Each window is offset by its first value before L1 comparison. Recursion insets peak-adjacent ends by floor(window / 2), retaining the outer trace endpoints.

required
trim int

Rows removed at each segment end for DTW only. Fewer than two remaining rows leaves the segment unassigned.

required
peak_height float

Minimum geometric L1 peak height in scaled signals.

required
rho_min float

Lower bound on a row's DTW acceptance threshold.

required
rho_max float

Upper bound on that threshold.

required
time_column str

Timestamp column, not a regression feature.

't'
max_derivative int

Highest repeated backward difference divided by dt. The first max_derivative rows remain unassigned, not padded.

1
clustering_derivative int

Output derivative used for DTW; zero removes offsets from start/end-matched windows, higher orders retain them.

1
alpha float

Threshold multiplier of each segment's nearest-other score, clipped to [rho_min, rho_max].

2.0

Returns:

Type Description
FamosDiscovery

Partial location labels and training-only scales, retaining original

FamosDiscovery

trace boundaries. No accuracy, rank, or full-coverage gate is applied.

fit_famos_dynamics

fit_famos_dynamics(traces: Sequence[DataFrame], discovery: FamosDiscovery, *, order: int = 1) -> FamosDynamics

Fit y[t+1] = f_qt.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Original discovery frames in the same trace/row order.

required
discovery FamosDiscovery

Partial location assignments; null rows are not fitted.

required
order int

Number of output-history rows. History may cross location boundaries but never trace boundaries. Source and target must lie in the SAME discovered segment, even if adjacent segments share a label.

1

Returns:

Type Description
FamosDynamics

Least-squares dynamics and diagnostics without a rank or RMSE gate.

FamosDynamics

A location with no usable regression targets raises ValueError;

FamosDynamics

the independent discovery artifact remains usable.

fit_famos_transitions

fit_famos_transitions(traces: Sequence[DataFrame], *, output_features: Sequence[str], input_features: Sequence[str], n_locations: int, max_depth: int | None = None, min_samples_leaf: int = 1, random_state: int = 0) -> FamosTransitionSelector

Fit next-location trees from adjacent explicitly labeled training rows.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent eager frames in caller-supplied row order, with nullable integer location_id in [0, n_locations). A label governs the interval beginning at its row. Exclude pairs with either label null, without bridging gaps or trace boundaries. No timestamps or sampling periods are inferred; unrelated columns are ignored. FaMoS discovery labels can be supplied via discovery.to_labeled_frames(original_traces).

required
output_features Sequence[str]

Ordered current observed physical outputs, not next-row targets. At least one output is required.

required
input_features Sequence[str]

Ordered current exogenous controls; may be empty. Feature names must be unique and cannot include location_id.

required
n_locations int

Positive number of declared source locations. The caller is responsible for location meanings and source-time alignment.

required
max_depth int | None

Maximum depth of each source's sklearn decision tree.

None
min_samples_leaf int

Minimum training observations in each tree leaf.

1
random_state int

Seed controlling sklearn split tie breaking.

0

Returns:

Type Description
FamosTransitionSelector

Source-conditioned trees, including valid constant-target trees,

FamosTransitionSelector

with training-only maximum-absolute feature scales (zero becomes one).

FamosTransitionSelector

A source without any outgoing training pair raises ValueError

FamosTransitionSelector

rather than acquiring an invented self loop.

MMSR Identification

Multi-modal symbolic regression for known-K scalar input-output mappings.

CSR jointly learns symbolic expressions, soft memberships, and variances. Subsequent transition modeling learns source-conditioned symbolic guards. Both stages return inspectable artifacts, not a universal hybrid runtime. PySR/Julia are loaded only when fitting, via the pysr optional extra.

Classes

MMSRCSR dataclass

MMSRCSR(input_features: tuple[str, ...], output_feature: str, variances: _Array, memberships: tuple[_Array, ...], diagnostics: CSRDiagnostics, _functions: tuple[_Expression, ...])

Scalar symbolic mappings, residual variances, and soft memberships.

Locations are positional integers in expressions and variances. memberships[i] has shape (trace_i.height, n_locations) in the original row order. No observations are dropped. Variances are in squared output units. Expressions alone do not classify locations: posterior membership also requires the observed target.

Attributes
input_features instance-attribute
input_features: tuple[str, ...]
output_feature instance-attribute
output_feature: str
variances instance-attribute
variances: _Array
memberships instance-attribute
memberships: tuple[_Array, ...]
diagnostics instance-attribute
diagnostics: CSRDiagnostics
expressions property
expressions: tuple[Expr, ...]

Selected SymPy mappings, one per location.

complexities property
complexities: tuple[int, ...]

Original search-tree node counts, before SymPy simplification.

Methods:
predict
predict(inputs: DataFrame) -> _Array

Evaluate every mapping; return shape (n_rows, n_locations).

Only input_features are read. Nonfinite expression outputs raise ValueError, including rational singularities outside training.

membership
membership(observations: DataFrame) -> _Array

Evaluate posterior memberships from regressors AND observed targets.

Returns shape (n_rows, n_locations). This is not an input-only location classifier and does not perform temporal inference.

CSRDiagnostics dataclass

CSRDiagnostics(completed_sweeps: int, updates: int, stop_reason: Literal['max_sweeps', 'error_stable', 'no_usable_candidate'], error: float, aic: float, failed_location: int | None = None)

Stopping information, not a certificate of distinct location recovery.

updates counts committed sequential replacements, excluding initialization. error is the final responsibility-weighted sum of absolute residuals; aic uses the total original tree complexity. failed_location is set only for an entirely unusable update frontier.

Attributes
completed_sweeps instance-attribute
completed_sweeps: int
updates instance-attribute
updates: int
stop_reason instance-attribute
stop_reason: Literal['max_sweeps', 'error_stable', 'no_usable_candidate']
error instance-attribute
error: float
aic instance-attribute
aic: float
failed_location class-attribute instance-attribute
failed_location: int | None = None

MMSRTransition dataclass

MMSRTransition(source_location: int, destination_location: int, loss: float, aic: float, _function: _Expression)

One ordered nonself edge with raw expression and zero-threshold guard.

loss is the source-balanced weighted mean squared error of the sigmoid of expression against the soft successor destination membership. complexity counts the original raw search tree, not the fixed sigmoid.

Attributes
source_location instance-attribute
source_location: int
destination_location instance-attribute
destination_location: int
loss instance-attribute
loss: float
aic instance-attribute
aic: float
expression property
expression: Expr

Raw symbolic expression, without the training sigmoid wrapper.

complexity property
complexity: int

Original raw search-tree node count.

Methods:
guard
guard(inputs: DataFrame) -> NDArray[bool_]

Evaluate expression >= 0 at source-time regressors.

Raises ValueError for nonfinite expression outputs. An active guard is not a physical reset or an event-localized continuous-time switch.

MMSRTransitions dataclass

MMSRTransitions(input_features: tuple[str, ...], edges: tuple[MMSRTransition, ...])

All ordered nonself edges; no implicit self edges or execution policy.

Multiple outgoing guards may be active together. Arbitration and probability propagation are deliberately not defined by this artifact.

Attributes
input_features instance-attribute
input_features: tuple[str, ...]
edges instance-attribute
edges: tuple[MMSRTransition, ...]

Functions:

fit_mmsr_csr

fit_mmsr_csr(traces: Sequence[DataFrame], *, input_features: Sequence[str], output_feature: str, n_locations: int, search_factory: _SearchFactory, max_sweeps: int = 10, random_state: int = 0, variance_floor: float = 1e-12) -> MMSRCSR

Jointly fit symbolic mappings, memberships, and residual variances.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

Independent eager frames of aligned scalar regression rows. Inputs may be measured states. No time, derivative, lag, or sampling period is inferred. Ordering matters only for subsequent TM.

required
input_features Sequence[str]

Ordered regressor columns.

required
output_feature str

One observed scalar target column.

required
n_locations int

Known positive number of locations.

required
search_factory _SearchFactory

Zero-argument factory returning a fresh configured pysr.PySRRegressor. Requires flowcean[pysr]. The factory owns operators, budgets, and seeds. MMSR owns weighted loss, ordinary scalar expressions, tree-node complexity, candidate selection, and temporary output storage. Leave custom loss, expression/type specs, complexity overrides, denoising, feature selection, guesses, and warm starts unset. Backend errors propagate.

required
max_sweeps int

Finite cap on complete sequential update sweeps.

10
random_state int

Seed for Dirichlet(1, ..., 1) initial memberships, separate from the factory's search seeds.

0
variance_floor float

Positive floor in squared output units; no rescaling.

1e-12

Returns:

Type Description
MMSRCSR

Independently usable CSR artifact. A wholly unusable proposal frontier

MMSRCSR

preserves the last valid mixture and reports no_usable_candidate;

MMSRCSR

invalid initialization or committed mixtures raise ValueError.

Notes

Follows Ly and Lipson's MMSR (JMLR 2012), with PySR replacing Eureqa. Local search minimizes weighted mean log1p(abs(residual)). Initialization interprets the paper's unspecified local AIC as 2*c + N*log(local_mean_loss). Updates select from the fresh frontier by complete-mixture AIC with total tree complexity and the sum of responsibility-weighted absolute residuals, not by local loss. Gaussian memberships have variance prefactors and no frequency priors; variances use the paper's unbiased weighted estimator, not MLE.

Finite searches can worsen the incumbent: no incumbent injection, monotonicity gate, restart, or best-ever return is used. Five complete sweeps with relative error change below 2% stop as error_stable; collapsed or inaccurate mappings can also be stable. Numerical support guards are 1e-12 for mass and variance denominator, effective sample size must exceed 1 + 1e-10, and logarithms floor error at 1e-300.

fit_mmsr_transitions

fit_mmsr_transitions(traces: Sequence[DataFrame], csr: MMSRCSR, *, search_factory: _SearchFactory) -> MMSRTransitions

Learn every source-conditioned next-location guard after CSR.

Parameters:

Name Type Description Default
traces Sequence[DataFrame]

The original ordered CSR traces. Adjacent rows are paired only within each independent trace; no timestamp or sampling period is inferred. Lengths and columns are checked, not dataset identity.

required
csr MMSRCSR

Independently fitted CSR artifact with soft row memberships.

required
search_factory _SearchFactory

Fresh configured pysr.PySRRegressor factory, with the same controlled settings as fit_mmsr_csr. May use different budgets and operators from the CSR factory.

required

Returns:

Type Description
MMSRTransitions

All K(K-1) directed nonself guards. For each source k and destination j,

MMSRTransitions

the sigmoid of the raw expression at u_n targets gamma[j, n+1].

MMSRTransitions

Weights balance source departure against source stay, not destination

MMSRTransitions

classes; all outgoing edges of one source share the same weights.

MMSRTransitions

Selection uses 2*c + N_pairs*log(weighted_mean_squared_error).

Raises:

Type Description
ValueError

Missing departure/stay evidence or an entirely unusable edge frontier, with the source or edge identified. No partial edge collection is returned; the separate CSR artifact remains usable.

Notes

This explicitly chooses the paper's source-time TM alignment: u_n and location n predict location n+1. The paper's formal mapping convention elsewhere differs. No reset map, continuous-time model conversion, initialization rule, or general multi-edge rollout is inferred.