sindae.problem
The ProblemDefinition abstract base class is the
single entry point for declaring an ODE/DAE system to SiNDAE. Subclass it, implement the
three abstract methods, and pass an instance to any training or inference function.
The three required methods are:
build_trajectory(block, traj_idx)— write the base DAE (PyomoVars,DerivativeVars, and constraints) for one trajectory, leaving the unknown term as a free variablez. No neural network and no discretization here.get_input_vars(block, t)— the Pyomo vars fed into the network at timet.get_output_vars(block, t)— the Pyomo vars the network produces at timet.
Everything else has a sensible default: discretize applies Lagrange–Radau collocation,
get_obs_vars defaults to the NN inputs, and get_aux_vars defaults to none. Implement
the optional add_true_output_constraints only if you want to synthesize data with
generate_data.
Usage¶
A minimal one-state ODE, dx/dt = z, where z is the unknown rate the network will
learn:
import numpy as np
import pyomo.environ as pyo
import pyomo.dae as dae
from sindae.problem import ProblemDefinition
class ExponentialDecay(ProblemDefinition):
def build_trajectory(self, block, traj_idx):
t0 = self.t_span[0]
block.t = dae.ContinuousSet(bounds=self.t_span)
block.x = pyo.Var(block.t, range(self.input_dim), initialize=1.0)
block.z = pyo.Var(block.t, range(self.z_dim), initialize=0.0)
block.dxdt = dae.DerivativeVar(block.x, wrt=block.t)
@block.Constraint(block.t, range(self.input_dim))
def ode(b, t, i):
return b.dxdt[t, i] == b.z[t, 0] # z is supplied by the network
block.x[t0, 0].fix(float(self.ics[traj_idx, 0]))
def get_input_vars(self, block, t):
return [block.x[t, 0]] # NN input = state x
def get_output_vars(self, block, t):
return [block.z[t, 0]] # NN output = learned rate z
problem = ExponentialDecay(
ics=np.array([[1.0]]), # one trajectory, x(0) = 1
input_dim=1, z_dim=1,
t_span=(0.0, 5.0), nfe=20, ncp=3,
)See the examples gallery for complete ODE and DAE problems, and Defining a Network Architecture for the network side.
API reference¶
ProblemDefinition¶
class ProblemDefinition(
ics: np.ndarray,
input_dim: int,
z_dim: int,
t_span: tuple,
nfe: int,
ncp: int,
obs_times: Optional[List[np.ndarray]] = None,
obs_values: Optional[List[np.ndarray]] = None,
obs_dim: Optional[int] = None,
aux_vars_dim: Optional[int] = None,
)Base class for a Neural DAE problem.
The user subclasses this and implements: build_trajectory(block, traj_idx) — base DAE (no NN, no discretization) get_input_vars(block, t) — raw Pyomo vars fed into the NN get_output_vars(block, t) — raw Pyomo vars produced by the NN get_obs_vars(block, t) — observed vars (default: same as get_input_vars) get_aux_vars(block, t) — extra vars to track (default: none)
Discretization uses Lagrange-Radau collocation by default; override
discretize for custom schemes.
Parameters
ics(np.ndarray) — Initial conditions, shape (num_trajectories, state_dim).input_dim(int) — Dimension of NN inputs (= len(get_input_vars(block, t))).z_dim(int) — Dimension of NN outputs (= len(get_output_vars(block, t))).t_span(tuple) — Time horizon (t0, tf).nfe(int) — Number of finite elements for Radau collocation.ncp(int) — Number of collocation points per finite element.obs_times(Optional[List[np.ndarray]], defaultNone) — Observed time points per trajectory, shape (T_obs,) each.obs_values(Optional[List[np.ndarray]], defaultNone) — Observed values per trajectory, shape (T_obs, obs_dim) each.obs_dim(Optional[int], defaultNone)aux_vars_dim(Optional[int], defaultNone)
Properties
obs_mean— Mean of observed values across all trajectories (obs_dim,).obs_std— Std of observed values across all trajectories (obs_dim,), clipped to 1e-8.
Methods
build_trajectory¶
build_trajectory(block: pyo.Block, traj_idx: int) -> NoneAdd base DAE variables and constraints to block (no NN, no discretization).
Parameters
block(pyo.Block)traj_idx(int)
get_input_vars¶
get_input_vars(block: pyo.Block, t) -> listReturn the list of raw Pyomo Var objects fed into the NN at time t.
Parameters
block(pyo.Block)t
Returns
list
get_output_vars¶
get_output_vars(block: pyo.Block, t) -> listReturn the list of raw Pyomo Var objects produced by the NN at time t.
Parameters
block(pyo.Block)t
Returns
list
discretize¶
discretize(model: pyo.ConcreteModel) -> NoneDiscretize all trajectories with Lagrange-Radau collocation.
Override for non-standard schemes (e.g. different collocation type, per-trajectory nfe/ncp, or non-DAE problems).
Parameters
model(pyo.ConcreteModel)
get_obs_vars¶
get_obs_vars(block: pyo.Block, t) -> listReturn observed Pyomo vars used in the data-fit objective at time t. Default: same as get_input_vars. Override when obs != NN inputs.
Parameters
block(pyo.Block)t
Returns
list
get_aux_vars¶
get_aux_vars(block: pyo.Block, t) -> listReturn additional Pyomo vars to track in InstanceData (e.g. algebraic vars). Default: none. Override to populate TrajectoryData.aux_vars.
Parameters
block(pyo.Block)t
Returns
list
add_true_output_constraints¶
add_true_output_constraints(block: pyo.Block) -> NoneAdd constraints pinning the output vars to the true formula.
Called pre-discretisation (block.t is still a ContinuousSet). Used only by generate_data — not part of normal training.
Parameters
block(pyo.Block)