sindae.algorithms.simultaneous
The simultaneous approach embeds the NN weights and biases directly in a single large NLP
and optimizes states, NN outputs, and NN parameters jointly in one solver call — no
outer training loop. Two backends are available, selected by
SimultaneousConfig.use_gbm:
use_gbm | Backend | Solver | Hessian |
|---|---|---|---|
False (default) | expression-writing | POUNCE | exact |
True | grey-box (GBM) | POUNCE | L-BFGS (limited-memory) |
The expression-writing backend rewrites the SimpleMLP as explicit Pyomo expressions and
gets an exact Hessian; the grey-box backend treats the network as a black box (function +
Jacobian) and works with any smooth Equinox module. See
Defining a Network Architecture for the trade-offs.
Usage¶
from sindae import extract_instance_data
from sindae.algorithms.simultaneous.train import SimultaneousConfig, solve_simultaneous
cfg = SimultaneousConfig(use_gbm=False, reg_coef=1e-3) # expression-writing, exact Hessian
trained_m, mlp = solve_simultaneous(
problem, mlp, cfg,
data=smoother_data, # normalization statistics
smoother_model=smoother_m, # reuse the discretized smoother as a warm start
solver_options={'tol': 1e-6, 'max_iter': 1000},
)
trained_data = extract_instance_data(problem, trained_m)For problems whose exact Hessian is awkward (e.g. ratio terms like ), switch to the grey-box variant with a limited-memory Hessian:
cfg = SimultaneousConfig(use_gbm=True, reg_coef=1e-3)
trained_m, mlp = solve_simultaneous(
problem, mlp, cfg, data=smoother_data, smoother_model=smoother_m,
solver_options={'tol': 1e-6, 'max_iter': 1000,
'hessian_approximation': 'limited-memory'},
)API reference¶
SimultaneousConfig¶
class SimultaneousConfig(use_gbm: bool = False, reg_coef: float = 0.0)Hyperparameters for the simultaneous (single-NLP) training approach.
Fields
use_gbm(bool, defaultFalse)reg_coef(float, default0.0)
build_simultaneous_model¶
build_simultaneous_model(
problem: ProblemDefinition,
mlp: SimpleMLP,
traj_indices: List[int],
data: InstanceData,
smoother_model: Optional[pyo.ConcreteModel] = None,
reg_coef: float = 0.0,
unfix_io: bool = True,
) -> pyo.ConcreteModelBuild a simultaneous NLP using expression-writing.
NN weights and biases are Pyomo decision variables (as an NNBlock).
The NN forward pass is written symbolically as Pyomo arithmetic expressions,
yielding exact second-order information (Hessian available for IPOPT).
Parameters
problem(ProblemDefinition)mlp(SimpleMLP)traj_indices(List[int])data(InstanceData) — Provides normalization statistics (input_mean/std, output_mean/std).smoother_model(Optional[pyo.ConcreteModel], defaultNone) — When provided, reuses the solved smoother NLP in-place (no rebuild / re-discretisation); IPOPT warm-starts from the smoother solution.reg_coef(float, default0.0) — L2 regularisation coefficient on all NN weights and biases.unfix_io(bool, defaultTrue)
Returns
m(pyo.ConcreteModel) — Extra Python attributes:m._nn_block: NNBlock (Pyomo weight/bias Vars)m._traj_t_sorted: List[List[float]]m._traj_norm_target: List[np.ndarray]
build_simultaneous_model_gbm¶
build_simultaneous_model_gbm(
problem: ProblemDefinition,
mlp: SimpleMLP,
traj_indices: List[int],
data: InstanceData,
smoother_model: Optional[pyo.ConcreteModel] = None,
reg_coef: float = 0.0,
unfix_io: bool = True,
) -> pyo.ConcreteModelBuild a simultaneous NLP using the grey-box (GBM) formulation.
The NN parameters theta are flat Pyomo Var objects (m.nn_params).
NNSimulGreyBoxModel evaluates NN(norm_input; theta) and provides
the Jacobian w.r.t. both norm_input and theta via JAX.
Because no Hessian is provided, IPOPT must use L-BFGS
(hessian_approximation='limited-memory').
Parameters
problem(ProblemDefinition)mlp(SimpleMLP)traj_indices(List[int])data(InstanceData) — Provides normalization statistics (input_mean/std, output_mean/std).smoother_model(Optional[pyo.ConcreteModel], defaultNone)reg_coef(float, default0.0)unfix_io(bool, defaultTrue)
Returns
m(pyo.ConcreteModel) — Extra Python attributes:m.nn_params: pyo.Var (flat theta)m._nn_params_unflatten: callable flat->SimpleMLP (for extract_mlp)m._traj_t_sorted: List[List[float]]m._traj_norm_target: List[np.ndarray]
extract_mlp¶
extract_mlp(m: pyo.ConcreteModel) -> SimpleMLPExtract a SimpleMLP with the optimised weights from a solved simultaneous model.
Works for both the expression-writing path (reads Pyomo NNBlock Var values) and the GBM path (reads flat Pyomo Var values).
Parameters
m(pyo.ConcreteModel)
Returns
SimpleMLP
solve_simultaneous¶
solve_simultaneous(
problem: ProblemDefinition,
mlp: SimpleMLP,
cfg: SimultaneousConfig,
data: InstanceData,
smoother_model: Optional[pyo.ConcreteModel] = None,
solver_options: Optional[dict] = None,
nlp_solver: Optional[str] = None,
traj_indices: Optional[List[int]] = None,
tee: bool = False,
timer: Optional[HierarchicalTimer] = None,
unfix_io: bool = True,
) -> Tuple[pyo.ConcreteModel, SimpleMLP]Build and solve the simultaneous NLP, returning the solved model and the trained SimpleMLP.
Parameters
problem(ProblemDefinition)mlp(SimpleMLP)cfg(SimultaneousConfig) — Algorithm hyperparameters (use_gbm,reg_coef).data(InstanceData) — Provides normalization statistics (input_mean/std, output_mean/std).smoother_model(Optional[pyo.ConcreteModel], defaultNone) — Solved smoother model to reuse (warm-starts the simultaneous solve and avoids rebuilding / re-discretising the model).solver_options(Optional[dict], defaultNone) — Extra solver options, e.g.{'max_iter': 500, 'tol': 1e-6, 'hessian_approximation': 'limited-memory'}. Passed to the selected NLP backend on either path.nlp_solver(Optional[str], defaultNone) — NLP backend ('pounce'default,'ipopt'/'cyipopt'). Applies to both paths. Whencfg.use_gbmis True the backend must be grey-box-capable (POUNCE / cyipopt);'ipopt'is rejected there.traj_indices(Optional[List[int]], defaultNone)tee(bool, defaultFalse) — Stream solver output to stdout.timer(Optional[HierarchicalTimer], defaultNone) — Reuse an external timer; a fresh one is created when omitted.unfix_io(bool, defaultTrue) — Unfix the NN input/output variables before solving. Set False for partially observed problems: unmeasured states have no data anchor, and leaving their variables free makes the solve diverge.
Returns
m(pyo.ConcreteModel (solved; pass to ``extract_instance_data``))trained_mlp(SimpleMLP (optimised weights extracted from the NLP))