monee solver¶
The public names below are re-exported from monee.solver
(e.g. from monee.solver import GEKKOSolver, PyomoSolver). Most code
does not instantiate a back-end directly - pass a solver name such as
"ipopt" or "gurobi" to the top-level entry points and let
resolve_solver() pick the back-end.
Core¶
Back-end-agnostic machinery: the abstract SolverInterface,
the SolverResult returned by every solve, and the
shared solve-pipeline helpers (variable injection/withdrawal, islanding-aware
component pruning, warm starting, post-processing).
- class monee.solver.core.InterStepState[source]¶
Bases:
ABCAbstract base.
step: negative = relative to current, non-negative = absolute.
- class monee.solver.core.OperatorEquationAssembly[source]¶
Bases:
objectEquation-assembly passes shared by backends whose model object exposes operator-overloaded symbols plus GEKKO-style hooks (
m.sin/m.cos/…,m.Equation(s),m.Obj,m.Intermediate): the GEKKO and CasADi backends.monee’s component
equations()bodies are backend-agnostic - they build expressions from the injected variable objects and the math hooks passed in - so a single set of passes drives both backends. Pyomo and Gurobi construct constraints differently (their own intermediate handling / objective accumulation) and provide their own overrides instead of using this mixin.Hosts must set
self._simulation(the per-solve simulation flag, read by the branch pass to drop operational flow limits) and may override_pwl_impl().- process_equations_nodes_childs(m, network: monee.model.network.Network, nodes, ignored_nodes)[source]¶
- process_oxf_components(m, network: monee.model.network.Network, optimization_problem, period_index=None)[source]¶
- class monee.solver.core.PeriodState(networks: list, current_t: int, dt_h: float = 1.0, initial_state: dict | None = None)[source]¶
Bases:
InterStepStateAll period networks (multi-period).
getreturns live solver vars after injection, so reading from another period becomes an algebraic cross-period constraint. Both past and future absolute indices are accessible.Note
Absolute (non-negative)
stepindices are window-local underrun_mpc():self._networksis only the current rolling window’s networks, sostep=0means the first period of this window, not the global start of the run. This differs fromStepState, whose absolute indices stay globally meaningful via its dropped-prefix bookkeeping. Relative (negative) lookbacks behave identically in both. Prefer relative indices ininter_step_equationsif you need MPC-window-independent behaviour.
- class monee.solver.core.SinglePeriodSolverProtocol[source]¶
Bases:
objectDocuments the contract a solver backend must expose to act as a delegate inside
GekkoMultiPeriodSolver/PyomoMultiPeriodSolver. Not enforced at runtime.
- class monee.solver.core.SolverInterface[source]¶
Bases:
ABCAbstract base class for solver backends (GEKKO, Pyomo, …).
- static mark_temporal_components(network, ignored_nodes: set)[source]¶
Set
_temporal_activeon every model carrying a temporal method, so static-only constraints can be suppressed when coupling is active.- Return type:
- process_inter_period_equations(solver_obj, network: monee.model.network.Network, nodes, branches, compounds, ignored_nodes: set, period_state, optimization_problem=None, period_index=None)[source]¶
Collect
inter_period_equations+inter_temporal_equationsplus user temporal constraints for multi-period solves.
- process_inter_step_equations(solver_obj, network: monee.model.network.Network, nodes, branches, compounds, ignored_nodes: set, step_state, optimization_problem=None, period_index=None)[source]¶
Collect
inter_step_equations+inter_temporal_equationsplus user temporal constraints for timeseries solves.
- abstractmethod solve(input_network: monee.model.network.Network, optimization_problem: monee.problem.core.OptimizationProblem = None, draw_debug=False, exclude_unconnected_nodes=False, step_state=None, simulation=False, formulation=None)[source]¶
Solve the energy-flow / optimisation problem for input_network.
- Parameters:
input_network (
monee.model.network.Network) – The network to solve.optimization_problem (
monee.problem.core.OptimizationProblem) – Optional optimisation problem with objectives and constraints. IfNone, performs a plain energy-flow solve.draw_debug – If
True, emit debug output from the solver.exclude_unconnected_nodes – Legacy flag; prefer islanding config.
step_state – Inter-step state from the previous timeseries step.
simulation – If
True, square the model (pin phantom vars, drop operational flow limits) and solve it as a steady-state simulation. GEKKO runs this as IMODE=1 (falling back to IMODE=3 if not square); backends without a simulation mode ignore it.formulation – Solve-time formulation override - a registry key string (
"smooth_nlp","convex_miqcqp", …), aNetworkFormulation, or a sequence of either (merged left to right). Overrides the network-levelapply_formulationchoice; components without any choice fall back toDEFAULT_SIMULATION_FORMULATION.
- Returns:
monee.solver.core.SolverResult– ASolverResultwith updated variable values and result DataFrames.
- class monee.solver.core.SolverResult(network: monee.model.network.Network, dataframes: dict[str, pandas.DataFrame], objective: float | None, success: bool, violations: dict[str, float] = <factory>, solver_status: str | None = None, termination_condition: str | None = None, mode_used: str | None = None, infeasibility_report: object | None = None)[source]¶
Bases:
objectOutcome of a single solve.
objective=0.0for plain energy flow;Nonewhen not meaningful (e.g.MultiPeriodResult.get_period_result).Termination metadata¶
solver_statusandtermination_conditionmirror the PyomoSolverStatus/TerminationConditionstrings (orNoneif not populated by the backend). They let downstream consumers distinguish a converged-optimal solution from a witness incumbent Gurobi returns when it hits a time limit — the witness hassuccess=Trueand looks identical otherwise. Callers that care about convergence (e.g. the MC resilience pipeline, which must drop aborted samples rather than averaging in a non-converged shed value) can inspecttermination_conditionto detect the time-limit case.- infeasibility_report: object | None = None¶
Diagnostic infeasibility report when the solve failed (
success=False);Noneon success. Declaring it as a field gives both backends a single, documented place to surface the report instead of the GEKKO-raises / Pyomo-dynamic-attribute split. (GEKKO additionally raisesGekkoSolveErrorcarrying the same report.)
- mode_used: str | None = None¶
"simulation"(square IMODE=1 steady-state) or"optimization"(IMODE=3). When a simulation was requested but the model was not square, this reads"optimization"- the signal that the fast steady-state path silently fell back.Nonewhen the backend doesn’t distinguish the two (e.g. Pyomo).- Type:
Which solve mode actually ran
- class monee.solver.core.StepState(initial_state: dict | None = None, max_steps: int | None = None)[source]¶
Bases:
InterStepStateAll previously-solved network copies (sequential timeseries).
getreturns floats.initial_statefalls back when no prior solve has written the attribute.max_stepscaps how many solved networks are retained (None= unlimited): each network is a full copy, so an open-ended run (e.g. aStepperpaced by a co-simulation framework) would otherwise grow memory without bound. Absolutestepindices keep their meaning when old networks are dropped; reading a dropped step falls back toinitial_state.
- monee.solver.core.apply_post_process(model: monee.model.core.GenericModel)[source]¶
Evaluate the model’s
PostProcessattributes from its solved values - runs afterwithdraw_varsand fully outside the solver. Lambdas receive a namespace, so they read fields asv.vm_pu(notv["vm_pu"]).- Return type:
- monee.solver.core.apply_post_process_all(nodes, branches, compounds, network)[source]¶
Run
apply_post_process()over every solved component.- Return type:
- monee.solver.core.col_summary(series: pandas.Series)¶
One-line numeric summary for a single attribute column.
Returns
'val'for a constant column,'[lo, hi]'when values vary, orNonewhen the series is empty or entirely NaN.
- monee.solver.core.compute_bound_violations(nodes, branches, compounds, network, tol: float = 1e-06)[source]¶
{"<Type>.<id>.<attr>": magnitude}for Var.value violations beyond tol.
- monee.solver.core.display_df(df: pandas.DataFrame)¶
Return df with internal bookkeeping columns removed.
- Return type:
- monee.solver.core.filter_bool_eqs(eqs, context=None)[source]¶
Drop Python-
boolsentinel equations, identically for both backends.An
equations()body can collapse a constraint to a Pythonboolwhen a component is over-deactivated (e.g. an attribute overwritten by aConstsoa == bevaluates eagerly):True→ tautology, a no-op; dropped silently.False→ structurally infeasible (contradictory) constraint. Feeding it into model construction either crashes the build (GEKKO) or - if it were kept - injects an unsatisfiable constraint. We drop it so the load-shedding objective can resolve the situation, but emit a warning so the modelling error is surfaced rather than silently hidden.
context is an optional label (e.g.
"node_3") included in the warning.
- monee.solver.core.find_ignored_nodes(network: monee.model.network.Network, islanding_config=None)[source]¶
Return node IDs to exclude from the solve.
Default: active topology, only ExtPowerGrid/ExtHydrGrid children are “leading”. With islanding_config: full topology (backup lines included) and any
GridFormingMixinchild counts as leading for an islanding-enabled carrier.
- monee.solver.core.ignore_branch(branch, network: monee.model.network.Network, ignored_nodes)[source]¶
- monee.solver.core.ignore_node(node, network: monee.model.network.Network, ignored_nodes)[source]¶
- monee.solver.core.inject_nans(target: monee.model.core.GenericModel)[source]¶
Replace Var/Const fields with NaN placeholders; zero regulation.
- monee.solver.core.inject_vars(inject_fn, nodes, branches, compounds, network, ignored_nodes)[source]¶
Call
inject_fn(model, component, category)on each active component; ignored components getinject_nans()instead.category∈ {branch,node,child,compound}.
- monee.solver.core.mark_he_flow_prescription(network: monee.model.network.Network, ignored_nodes)[source]¶
Decide for each dynamic heat exchanger (compound-internal
SubHE) whether it prescribes its design mass flow or yields to the network.A SubHE bridges water islands through its multi-grid control node (the islands stay separate because
_carrier_islands()only spans pure water nodes). In a supply/return structure every adjacent island has a slack child with a free mass flow (ExtHydrGrid/ConsumeHydrGrid), so the through-flow is otherwise undetermined and the design flow must prescribe it. If any adjacent island lacks such a slack (e.g. a fixed-mass-flowSinkfed only through the compound), that island’s balance already fixes the through-flow - pinning it to the design flow would over-determine the system, so the HE yields and its energy balance runs on the actual flow instead (_he_flow_prescribed = False).
- monee.solver.core.mark_heat_balance_slacks(network: monee.model.network.Network, ignored_nodes)[source]¶
Mark one grid-forming reference node per energized heat island so its (dependent) nodal heat balance is dropped - the heat carrier’s slack, mirroring the free mass-flow / angle slack the other carriers already have.
The nodal heat balances over a connected island are linearly dependent (one is redundant); the grid-forming node absorbs the imbalance. Islands are connected components of the active water subgraph; references are
GridFormingMixinchildren, matching the islanding extension’s criterion.
- monee.solver.core.mark_ignored_components(network, ignored_nodes)[source]¶
Pre-mark
component.ignoredsooptimization_problem._applyexcludes them.
- monee.solver.core.persist_solution(solved_copy: monee.model.network.Network, original: monee.model.network.Network)[source]¶
Propagate solved values back so the next
inject_varswarm-starts.- Return type:
- monee.solver.core.pin_floating_hydraulic_gauges(network: monee.model.network.Network, ignored_nodes)[source]¶
Pin one node’s pressure per energized gas/water island that has no grid-forming source.
A grid-forming child (
ExtHydrGrid/GridFormingSource) pins an absolute pressure reference via itsoverwrite. An island with none references pressure only through the (relative) pipe pressure-drop equations - its absolute level is a free gauge degree of freedom, which IPOPT can only resolve by regularising a rank-deficient block (slow, fragile). Such islands arise routinely: e.g. a heat-exchanger-fed return loop, where the coupling HE transfers mass and temperature but imposes no pressure drop.Pinning any one node’s pressure to the carrier nominal removes the rank-deficiency without changing flows or pressure differences (only the arbitrary absolute level). Temperature is left free: where present it is referenced through the heat-exchanger coupling, not a nodal pin.
- monee.solver.core.remove_cps(network: monee.model.network.Network)[source]¶
GEKKO back-end¶
- class monee.solver.gekko.GEKKOSolver(solver=1)[source]¶
Bases:
OperatorEquationAssembly,SolverInterface- static inject_gekko_vars_attr(gekko: gekko.gekko.GEKKO, target: monee.model.core.GenericModel, id, name_map=None)[source]¶
- solve(input_network: monee.model.network.Network, optimization_problem: monee.problem.core.OptimizationProblem = None, draw_debug=False, exclude_unconnected_nodes=False, step_state: monee.solver.core.StepState = None, simulation=False, formulation=None)[source]¶
Solve the energy-flow / optimisation problem for input_network.
- Parameters:
input_network (
monee.model.network.Network) – The network to solve.optimization_problem (
monee.problem.core.OptimizationProblem) – Optional optimisation problem with objectives and constraints. IfNone, performs a plain energy-flow solve.draw_debug – If
True, emit debug output from the solver.exclude_unconnected_nodes – Legacy flag; prefer islanding config.
step_state (
monee.solver.core.StepState) – Inter-step state from the previous timeseries step.simulation – If
True, square the model (pin phantom vars, drop operational flow limits) and solve it as a steady-state simulation. GEKKO runs this as IMODE=1 (falling back to IMODE=3 if not square); backends without a simulation mode ignore it.formulation – Solve-time formulation override - a registry key string (
"smooth_nlp","convex_miqcqp", …), aNetworkFormulation, or a sequence of either (merged left to right). Overrides the network-levelapply_formulationchoice; components without any choice fall back toDEFAULT_SIMULATION_FORMULATION.
- Returns:
A
SolverResultwith updated variable values and result DataFrames.
- static withdraw_gekko_vars_attr(target: monee.model.core.GenericModel)[source]¶
Pyomo back-end¶
- class monee.solver.pyo.PyomoPWLImpl(pm: pyomo.core.base.PyomoModel.ConcreteModel, pw_repn: str = 'SOS2')[source]¶
Bases:
object
- class monee.solver.pyo.PyomoSolver(solver_name: str = 'scip')[source]¶
Bases:
SolverInterfacePyomo-backed solver.
solver_nameis overridable persolve().- static inject_pyomo_vars_attr(pm: pyomo.core.base.PyomoModel.ConcreteModel, target: monee.model.core.GenericModel, prefix: str)[source]¶
Replace Var/Const with Pyomo Var / numeric. Clamps stale
initializeinto[min, max](suppresses W1002 when bounds tightened since last solve).
- process_equations_nodes_childs(pm, network: monee.model.network.Network, nodes, ignored_nodes)[source]¶
- process_oxf_components(pm, network, optimization_problem: monee.problem.core.OptimizationProblem, period_index=None)[source]¶
- solve(input_network: monee.model.network.Network, optimization_problem: monee.problem.core.OptimizationProblem = None, draw_debug=False, exclude_unconnected_nodes: bool = False, step_state: monee.solver.core.StepState = None, simulation: bool = False, formulation=None, solver_name: str | None = None, **kwargs)[source]¶
Solve the energy-flow / optimisation problem for input_network.
- Parameters:
input_network (
monee.model.network.Network) – The network to solve.optimization_problem (
monee.problem.core.OptimizationProblem) – Optional optimisation problem with objectives and constraints. IfNone, performs a plain energy-flow solve.draw_debug – If
True, emit debug output from the solver.exclude_unconnected_nodes (
bool) – Legacy flag; prefer islanding config.step_state (
monee.solver.core.StepState) – Inter-step state from the previous timeseries step.simulation (
bool) – IfTrue, square the model (pin phantom vars, drop operational flow limits) and solve it as a steady-state simulation. GEKKO runs this as IMODE=1 (falling back to IMODE=3 if not square); backends without a simulation mode ignore it.formulation – Solve-time formulation override - a registry key string (
"smooth_nlp","convex_miqcqp", …), aNetworkFormulation, or a sequence of either (merged left to right). Overrides the network-levelapply_formulationchoice; components without any choice fall back toDEFAULT_SIMULATION_FORMULATION.
- Returns:
A
SolverResultwith updated variable values and result DataFrames.
- static withdraw_pyomo_vars_attr(target: monee.model.core.GenericModel)[source]¶
Pyomo Var →
Var. Restoresinteger, snaps bound-noise to bounds, replaces NaN/None with 0 so the next solve’s warmstart survives.
Dispatch¶
Solver dispatch - resolve (solver, backend) to a concrete solver.
Concrete instances pass through unchanged. IPOPT (the default solver) routes to the in-process CasADi backend when installed (falling back to GEKKO’s bundled IPOPT otherwise); the discrete GEKKO solvers (APOPT/BPOPT) route to GEKKO; any other name is forwarded to Pyomo. Default is therefore CasADi/IPOPT (GEKKO/IPOPT without casadi).
backend='gurobipy' selects the native in-memory Gurobi backend
(GurobipySolver) instead of driving Gurobi
through Pyomo’s file round-trip; it is single-period only and the solver name
must be 'gurobi' (the sole solver it provides).
backend='casadi' selects the in-process CasADi/IPOPT backend
(CasADiSolver), which builds the NLP once as an
in-memory expression graph and calls IPOPT in-process (no subprocess). The
solver name must be 'ipopt' (the sole solver it provides). It supports
single-period solves, temporal timeseries (storage/linepack/LTC) via the
per-step loop, and multi-period
(CasADiMultiPeriodSolver); for memory-less
timeseries it additionally enables a build-once / re-solve graph-reuse fast path
in monee.run_timeseries().
- monee.solver.dispatch.resolve_multi_period_solver(solver=None, backend: str | None = None)[source]¶
Multi-period analogue of
resolve_solver().
- monee.solver.dispatch.resolve_solver(solver=None, backend: str | None = None)[source]¶
Single-step
SolverInterfacefor(solver, backend).- Return type:
- monee.solver.dispatch.GEKKO_SOLVERS: dict[str, int] = {"apopt": 1, "bpopt": 2, "ipopt": 3}¶
Mapping of GEKKO solver name to the GEKKO
SOLVERoption integer. These are the namesresolve_solver()routes to the GEKKO back-end.
Infeasibility diagnostics¶
Solver-specific infeasibility diagnostics.
pyo analyses a Pyomo model after an infeasible solve (constraint
residuals, bound violations, Minimal Infeasible Subsystem); apm
parses the APMonitor run-directory artifacts of a failed GEKKO solve
(IPOPT/APOPT). Both produce structured reports with a uniform summary().
Pyomo¶
Pyomo infeasibility diagnostics. Translates Pyomo var names like
child_3__p_mw back to monee component descriptions and wraps the
Pyomo analysis utilities.
- class monee.solver.infeasibility.pyo.BoundViolation(name: str, display_name: str, value: float, lower: float | None, upper: float | None, violation: float)[source]¶
Bases:
objectA variable violating or sitting on its bounds.
- class monee.solver.infeasibility.pyo.ConstraintResidual(index: int | str, body_value: float, lower: float | None, upper: float | None, residual: float, expression: str | None = None)[source]¶
Bases:
objectA constraint that is violated or close to violation.
- class monee.solver.infeasibility.pyo.InfeasibilityReport(constraint_residuals: list[monee.solver.infeasibility.pyo.ConstraintResidual] = <factory>, bound_violations: list[monee.solver.infeasibility.pyo.BoundViolation] = <factory>, variables_at_bounds: list[dict] = <factory>, mis_constraints: list[str] = <factory>)[source]¶
Bases:
objectStructured infeasibility report.
- bound_violations: list[BoundViolation]¶
- constraint_residuals: list[ConstraintResidual]¶
- monee.solver.infeasibility.pyo.collect_constraint_residuals(pm: pyomo.core.base.PyomoModel.ConcreteModel, tol: float = 0.0001)[source]¶
List constraints whose residual exceeds tol, sorted by magnitude.
- Return type:
- monee.solver.infeasibility.pyo.collect_variable_bound_violations(pm: pyomo.core.base.PyomoModel.ConcreteModel, tol: float = 0.0001)[source]¶
Return variables whose current value violates their bounds.
- Return type:
- monee.solver.infeasibility.pyo.collect_variables_at_bounds(pm: pyomo.core.base.PyomoModel.ConcreteModel, tol: float = 0.0001)[source]¶
List variables whose value sits within tol of a bound (active bounds).
- monee.solver.infeasibility.pyo.compute_mis(pm: pyomo.core.base.PyomoModel.ConcreteModel, solver_name: str = 'scip')[source]¶
Names/indices of constraints/bounds forming a Minimal Infeasible Subsystem. Needs SCIP/Gurobi/CPLEX. Each internal solve is capped at
_MIS_TIME_LIMIT_Sso a hard MIS computation can’t stall the caller.
- monee.solver.infeasibility.pyo.diagnose_infeasibility(pm: pyomo.core.base.PyomoModel.ConcreteModel, solver_name: str = 'scip', tol: float = 0.0001, compute_mis_flag: bool = True)[source]¶
Build an
InfeasibilityReport.compute_mis_flagtriggers the Minimal Infeasible Subsystem analysis (slow).- Return type:
GEKKO / APMonitor¶
GEKKO/APMonitor infeasibility diagnostics (IPOPT and APOPT).
On a failed local solve APMonitor leaves two artifacts in the run directory
(m._path) before GEKKO raises its bare @error: Solution Not Found:
infeasibilities.txt- the equations with the largest residuals at the final iterate, each with the involved variables (value, bounds, marginal),results.json- the final (failed) iterate for every variable.
This module parses both into a structured GekkoInfeasibilityReport
analogous to the Pyomo InfeasibilityReport.
APMonitor sanitises variable names with re.sub(r"\W+", "_", name).lower()
(powergenerator-5.p_mw → powergenerator_5_p_mw), which is not
reversible; callers therefore pass a name_map of sanitised → original monee
names, built while injecting the GEKKO variables.
- class monee.solver.infeasibility.apm.ApmEquationResidual(number: int, lower: float, residual: float, upper: float, infeasibility: float, equation: str, variables: list[monee.solver.infeasibility.apm.ApmVariableState] = <factory>)[source]¶
Bases:
objectAn equation flagged by APMonitor as possibly infeasible.
- variables: list[ApmVariableState]¶
- class monee.solver.infeasibility.apm.ApmVariableState(name: str, display_name: str, value: float, lower: float, upper: float, marginal: float)[source]¶
Bases:
objectA variable as listed under an infeasible equation.
- class monee.solver.infeasibility.apm.GekkoInfeasibilityReport(solver_message: str = '', infeasible_equations: list[monee.solver.infeasibility.apm.ApmEquationResidual] = <factory>, bound_violations: list[dict] = <factory>, variables_at_bounds: list[dict] = <factory>)[source]¶
Bases:
objectStructured report of a failed GEKKO/APMonitor solve.
- infeasible_equations: list[ApmEquationResidual]¶
- exception monee.solver.infeasibility.apm.GekkoSolveError(message: str, report: monee.solver.infeasibility.apm.GekkoInfeasibilityReport | None = None)[source]¶
Bases:
RuntimeErrorFailed GEKKO solve with attached
GekkoInfeasibilityReport.
- monee.solver.infeasibility.apm.collect_gekko_bound_violations(m, tol: float = 0.0001, name_map: dict[str, str] | None = None)[source]¶
Variables whose failed-iterate value violates their declared bounds.
- monee.solver.infeasibility.apm.collect_gekko_variables_at_bounds(m, tol: float = 0.0001, name_map: dict[str, str] | None = None)[source]¶
Variables whose failed-iterate value sits within tol of a bound.
- monee.solver.infeasibility.apm.diagnose_gekko_infeasibility(m, name_map: dict[str, str] | None = None, solver_message: str = '', tol: float = 0.0001)[source]¶
Build a
GekkoInfeasibilityReportfrom the run directory of a failed local solve. Best-effort: returnsNonewhen no artifacts exist (e.g. remote solve) and never raises.- Return type:
monee.solver.infeasibility.apm.GekkoInfeasibilityReport|None