fluxopt.flow_system
¶
FlowSystem — the declarative top of the element layer.
A FlowSystem is an inert, validated description of a flow system: the
same lists you would pass to :func:fluxopt.optimize, gathered into one object
that round-trips to dict/YAML. It carries structure (components, effects,
config) and ProfileRef references to time-series; the actual series are
supplied at solve time via profiles (system.optimize(profiles=...)) and
resolved into arrays just before the model is built.
The FlowSystem has no modeling behavior of its own — .optimize() runs the existing
pipeline (:meth:ModelData.build → the math program). Declaration (the system)
and use (building/solving) stay separate.
Classes:
| Name | Description |
|---|---|
FlowSystem |
A declarative flow-system description (see module docstring). |
FlowSystem
pydantic-model
¶
Bases: BaseModel
A declarative flow-system description (see module docstring).
Show JSON schema:
{
"$defs": {
"Carrier": {
"description": "Physical energy medium (electricity, heat, gas, \u2026).",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"nodes": {
"items": {
"type": "string"
},
"title": "Nodes",
"type": "array"
},
"unit": {
"default": "MWh",
"title": "Unit",
"type": "string"
},
"color": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Color"
},
"description": {
"default": "",
"title": "Description",
"type": "string"
}
},
"required": [
"id"
],
"title": "Carrier",
"type": "object"
},
"Converter": {
"description": "Conversion between input and output flows.\n\nTwo mutually exclusive modes:\n\n- **Linear** \u2014 ``conversion_factors=[{flow_short_id: a_f}, ...]``,\n one dict per equation; constraint ``sum_f(a_f * P_{f,t}) = 0``.\n- **Piecewise** \u2014 ``conversion=PiecewiseConversion(...)``; the solver\n interpolates between breakpoints, optionally with on/off via\n ``PiecewiseConversion.status``.",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"inputs": {
"items": {
"$ref": "#/$defs/Flow"
},
"title": "Inputs",
"type": "array"
},
"outputs": {
"items": {
"$ref": "#/$defs/Flow"
},
"title": "Outputs",
"type": "array"
},
"conversion_factors": {
"items": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"type": "object"
},
"title": "Conversion Factors",
"type": "array"
},
"conversion": {
"anyOf": [
{
"$ref": "#/$defs/PiecewiseConversion"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"id",
"inputs",
"outputs"
],
"title": "Converter",
"type": "object"
},
"Effect": {
"description": "A tracked quantity across the optimization horizon (cost, CO\u2082, \u2026).\n\nOne effect is designated as the objective to minimize via the\n``objective`` argument of ``optimize()``. Others can be bounded\nto enforce budgets (e.g. emission caps).\n\nEffects accumulate contributions from two domains:\n\n- **Temporal** \u2014 per-timestep flow costs, running costs, startup costs.\n- **Lump** \u2014 one-time sizing costs and fixed costs.\n\nCross-effect chains (e.g. CO\u2082 \u2192 cost) are supported via\n``contribution_from``.\n\nSee: docs/math/effects.md",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"unit": {
"default": "",
"title": "Unit",
"type": "string"
},
"total_max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Max"
},
"total_min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Total Min"
},
"periodic_max": {
"anyOf": [
{
"$ref": "#/$defs/Variate"
},
{
"type": "null"
}
],
"default": null
},
"periodic_min": {
"anyOf": [
{
"$ref": "#/$defs/Variate"
},
{
"type": "null"
}
],
"default": null
},
"contribution_from": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Contribution From",
"type": "object"
},
"period_weights": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Period Weights"
}
},
"required": [
"id"
],
"title": "Effect",
"type": "object"
},
"Flow": {
"description": "A single energy flow on a carrier.\n\n``short_id`` defaults to ``carrier`` (or ``carrier:node`` when ``node``\nis set). Set explicitly to disambiguate multiple flows on the same\ncarrier::\n\n Flow(carrier='elec') # short_id='elec'\n Flow(carrier='heat', node='A') # short_id='heat:A'\n Flow(carrier='elec', short_id='base') # short_id='base'\n\n``short_id`` must be unique within a component. The system-global\nqualified id ``component(short_id)`` is derived at build time and\nappears as the ``flow`` coordinate in ``ModelData`` and results; the\nflow itself is never modified. Storage resolves colliding short_ids\nto ``charge`` / ``discharge`` at that point.\n\nSee: docs/math/flows.md",
"properties": {
"carrier": {
"title": "Carrier",
"type": "string"
},
"short_id": {
"default": "",
"title": "Short Id",
"type": "string"
},
"node": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Node"
},
"size": {
"anyOf": [
{
"type": "number"
},
{
"$ref": "#/$defs/Sizing"
},
{
"$ref": "#/$defs/Investment"
},
{
"type": "null"
}
],
"default": null,
"title": "Size"
},
"relative_rate_min": {
"$ref": "#/$defs/Variate",
"default": 0.0
},
"relative_rate_max": {
"$ref": "#/$defs/Variate",
"default": 1.0
},
"fixed_relative_profile": {
"anyOf": [
{
"$ref": "#/$defs/Variate"
},
{
"type": "null"
}
],
"default": null
},
"effects_per_flow_hour": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Flow Hour",
"type": "object"
},
"flow_hours_min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Flow Hours Min"
},
"flow_hours_max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Flow Hours Max"
},
"load_factor_min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Load Factor Min"
},
"load_factor_max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Load Factor Max"
},
"ramp_up_per_hour": {
"anyOf": [
{
"$ref": "#/$defs/Variate"
},
{
"type": "null"
}
],
"default": null
},
"ramp_down_per_hour": {
"anyOf": [
{
"$ref": "#/$defs/Variate"
},
{
"type": "null"
}
],
"default": null
},
"status": {
"anyOf": [
{
"$ref": "#/$defs/Status"
},
{
"type": "null"
}
],
"default": null
},
"prior_rates": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Prior Rates"
}
},
"required": [
"carrier"
],
"title": "Flow",
"type": "object"
},
"Investment": {
"description": "Singular discrete build-timing optimization.\n\nThe solver decides WHEN to build (which period) and at what size.\nOnce built, capacity is available for ``lifetime`` periods.\nSize is decided once \u2014 no growth or partial retirement.",
"properties": {
"size_min": {
"minimum": 0,
"title": "Size Min",
"type": "number"
},
"size_max": {
"minimum": 0,
"title": "Size Max",
"type": "number"
},
"mandatory": {
"default": true,
"title": "Mandatory",
"type": "boolean"
},
"lifetime": {
"anyOf": [
{
"exclusiveMinimum": 0,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Lifetime"
},
"prior_size": {
"default": 0.0,
"minimum": 0,
"title": "Prior Size",
"type": "number"
},
"effects_per_size_at_build": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Size At Build",
"type": "object"
},
"effects_fixed_at_build": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Fixed At Build",
"type": "object"
},
"effects_per_size_recurring": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Size Recurring",
"type": "object"
},
"effects_fixed_recurring": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Fixed Recurring",
"type": "object"
}
},
"required": [
"size_min",
"size_max"
],
"title": "Investment",
"type": "object"
},
"PiecewiseConversion": {
"description": "Piecewise-linear conversion linking N flows.\n\nWraps :func:`linopy.piecewise.add_piecewise_formulation`. All flows\nshare interpolation weights \u2014 every operating point lies on the same\npiece of the curve.\n\nTwo input forms:\n\n- **Dict** \u2014 equality-only, terse for the common case::\n\n PiecewiseConversion(points={'fuel': [0, 50, 100], 'Heat': [0, 45, 70]})\n\n- **List of tuples** \u2014 supports per-flow inequality bounds::\n\n PiecewiseConversion(\n points=[\n ('fuel', [0, 50, 100]),\n ('Heat', [0, 45, 70], '>='),\n ]\n )\n\nSee: docs/math/converters.md",
"properties": {
"points": {
"anyOf": [
{
"additionalProperties": {
"items": {
"$ref": "#/$defs/Variate"
},
"type": "array"
},
"type": "object"
},
{
"items": {
"anyOf": [
{
"maxItems": 2,
"minItems": 2,
"prefixItems": [
{
"type": "string"
},
{
"items": {
"$ref": "#/$defs/Variate"
},
"type": "array"
}
],
"type": "array"
},
{
"maxItems": 3,
"minItems": 3,
"prefixItems": [
{
"type": "string"
},
{
"items": {
"$ref": "#/$defs/Variate"
},
"type": "array"
},
{
"enum": [
"==",
"<=",
">="
],
"type": "string"
}
],
"type": "array"
}
]
},
"type": "array"
}
],
"title": "Points"
},
"method": {
"$ref": "#/$defs/PiecewiseMethod",
"default": "auto"
},
"status": {
"anyOf": [
{
"$ref": "#/$defs/Status"
},
{
"type": "null"
}
],
"default": null
},
"availability": {
"$ref": "#/$defs/Variate",
"default": 1.0
}
},
"required": [
"points"
],
"title": "PiecewiseConversion",
"type": "object"
},
"PiecewiseMethod": {
"enum": [
"auto",
"sos2",
"incremental",
"lp"
],
"type": "string"
},
"Port": {
"description": "System boundary importing and exporting carriers.\n\n``imports`` bring flow into the system (sources); ``exports`` send it out\nof the system (sinks).",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"imports": {
"items": {
"$ref": "#/$defs/Flow"
},
"title": "Imports",
"type": "array"
},
"exports": {
"items": {
"$ref": "#/$defs/Flow"
},
"title": "Exports",
"type": "array"
}
},
"required": [
"id"
],
"title": "Port",
"type": "object"
},
"ProfileRef": {
"description": "Reference to a time-series stored outside the model definition.\n\nA serializable stand-in for an inline ``Variate`` array: the profile lives\nin a data file / dataset and is named here, so structural definitions\nround-trip to YAML/JSON without inlining 8760-point series. Resolve it to a\n:class:`xr.DataArray` with :meth:`resolve` before building the model.",
"properties": {
"dataset": {
"title": "Dataset",
"type": "string"
},
"variable": {
"title": "Variable",
"type": "string"
}
},
"required": [
"dataset",
"variable"
],
"title": "ProfileRef",
"type": "object"
},
"Sizing": {
"description": "Capacity optimization parameters.\n\nThe solver decides the optimal size within [size_min, size_max].\n\n- ``mandatory=True``: continuous, size in [min, max], no binary.\n- ``mandatory=False``: binary indicator gates size: 0 or [min, max].\n- ``size_min == size_max`` with ``mandatory=False``: binary invest\n at exact size (yes/no).\n\nSee: docs/math/sizing.md",
"properties": {
"size_min": {
"minimum": 0,
"title": "Size Min",
"type": "number"
},
"size_max": {
"minimum": 0,
"title": "Size Max",
"type": "number"
},
"mandatory": {
"default": true,
"title": "Mandatory",
"type": "boolean"
},
"effects_per_size": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Size",
"type": "object"
},
"effects_fixed": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Fixed",
"type": "object"
}
},
"required": [
"size_min",
"size_max"
],
"title": "Sizing",
"type": "object"
},
"Status": {
"description": "Binary on/off behavior parameters.\n\nTogether with relative bounds, gives semi-continuous behavior:\n``{0} U [min, max] * size``.\n\nSee: docs/math/status.md",
"properties": {
"uptime_min": {
"anyOf": [
{
"minimum": 0,
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Uptime Min"
},
"uptime_max": {
"anyOf": [
{
"minimum": 0,
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Uptime Max"
},
"downtime_min": {
"anyOf": [
{
"minimum": 0,
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Downtime Min"
},
"downtime_max": {
"anyOf": [
{
"minimum": 0,
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Downtime Max"
},
"effects_per_running_hour": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Running Hour",
"type": "object"
},
"effects_per_startup": {
"additionalProperties": {
"$ref": "#/$defs/Variate"
},
"title": "Effects Per Startup",
"type": "object"
}
},
"title": "Status",
"type": "object"
},
"Storage": {
"description": "Energy storage with level dynamics.\n\nFlow ids are qualified as ``storage(flow)`` at build time. When both\nflows share a short_id, the qualified ids use ``charge`` /\n``discharge`` instead (the flows themselves are never modified)::\n\n Storage(\n id='bat', charging=Flow(carrier='elec'), discharging=Flow(carrier='elec')\n ) # bat(charge), bat(discharge)\n Storage(\n id='bat',\n charging=Flow(carrier='elec', short_id='in'),\n discharging=Flow(carrier='elec', short_id='out'),\n ) # bat(in), bat(out)\n\nLevel balance::\n\n E_{s,t+1} = E_{s,t} (1 - \u03b4)^\u0394t + P^c \u03b7^c \u0394t - P^d / \u03b7^d \u0394t\n\nSee: docs/math/storage.md",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"charging": {
"$ref": "#/$defs/Flow"
},
"discharging": {
"$ref": "#/$defs/Flow"
},
"capacity": {
"anyOf": [
{
"type": "number"
},
{
"$ref": "#/$defs/Sizing"
},
{
"$ref": "#/$defs/Investment"
},
{
"type": "null"
}
],
"default": null,
"title": "Capacity"
},
"eta_charge": {
"$ref": "#/$defs/Variate",
"default": 1.0
},
"eta_discharge": {
"$ref": "#/$defs/Variate",
"default": 1.0
},
"relative_loss_per_hour": {
"$ref": "#/$defs/Variate",
"default": 0.0
},
"prior_level": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Prior Level"
},
"cyclic": {
"default": true,
"title": "Cyclic",
"type": "boolean"
},
"relative_level_min": {
"$ref": "#/$defs/Variate",
"default": 0.0
},
"relative_level_max": {
"$ref": "#/$defs/Variate",
"default": 1.0
},
"final_level_min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Final Level Min"
},
"final_level_max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Final Level Max"
},
"prevent_simultaneous": {
"default": false,
"title": "Prevent Simultaneous",
"type": "boolean"
},
"status": {
"anyOf": [
{
"$ref": "#/$defs/Status"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"id",
"charging",
"discharging"
],
"title": "Storage",
"type": "object"
},
"Timesteps": {
"anyOf": [
{
"items": {
"format": "date-time",
"type": "string"
},
"type": "array"
},
{
"items": {
"type": "integer"
},
"type": "array"
}
]
},
"Variate": {
"anyOf": [
{
"type": "number"
},
{
"type": "integer"
},
{
"items": {
"type": "number"
},
"type": "array"
},
{
"$ref": "#/$defs/ProfileRef"
}
]
}
},
"description": "A declarative flow-system description (see module docstring).",
"properties": {
"timesteps": {
"$ref": "#/$defs/Timesteps"
},
"carriers": {
"items": {
"$ref": "#/$defs/Carrier"
},
"title": "Carriers",
"type": "array"
},
"effects": {
"items": {
"$ref": "#/$defs/Effect"
},
"title": "Effects",
"type": "array"
},
"ports": {
"items": {
"$ref": "#/$defs/Port"
},
"title": "Ports",
"type": "array"
},
"objective": {
"anyOf": [
{
"type": "string"
},
{
"additionalProperties": {
"type": "number"
},
"type": "object"
}
],
"title": "Objective"
},
"converters": {
"items": {
"$ref": "#/$defs/Converter"
},
"title": "Converters",
"type": "array"
},
"storages": {
"items": {
"$ref": "#/$defs/Storage"
},
"title": "Storages",
"type": "array"
},
"dt": {
"anyOf": [
{
"type": "number"
},
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Dt"
},
"periods": {
"anyOf": [
{
"items": {
"type": "integer"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Periods"
},
"period_weights": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Period Weights"
}
},
"required": [
"timesteps",
"carriers",
"effects",
"ports",
"objective"
],
"title": "FlowSystem",
"type": "object"
}
Config:
default:_PYDANTIC_CFG
Fields:
-
timesteps(Timesteps) -
carriers(list[Carrier]) -
effects(list[Effect]) -
ports(list[Port]) -
objective(str | dict[str, float]) -
converters(list[Converter]) -
storages(list[Storage]) -
dt(float | list[float] | None) -
periods(list[int] | None) -
period_weights(list[float] | None)
Validators:
-
_validate_references
objective
pydantic-field
¶
Effect(s) to minimize — a name or {effect: weight}. Must name at
least one non-penalty effect.
converters
pydantic-field
¶
Linear/piecewise converters between carriers.
dt
pydantic-field
¶
Timestep duration in hours. Auto-derived if None.
periods
pydantic-field
¶
Integer period labels for multi-period optimization.
period_weights
pydantic-field
¶
Explicit weights per period. Inferred from gaps if None.
from_dict
classmethod
¶
from_dict(data: Mapping[str, Any]) -> FlowSystem
from_yaml
classmethod
¶
from_yaml(path: str | Path) -> FlowSystem
to_dict
¶
to_yaml
¶
required_profiles
¶
Enumerate the external data this system needs, as {dataset: variables}.
The contract a profiles supply must cover before
:meth:build_model / :meth:optimize can run. Empty when every value
is inline.
Source code in src/fluxopt/flow_system.py
math
¶
math() -> Any
The equations this system will be solved as, as data.
An :class:lpspec.Model — the whole program, before any of this
system's numbers are bound to it. Read it (to_yaml()), typeset it
(mathspec.to_latex(...)), or edit it and hand it back to
:meth:optimize as math=: adding a named quantity to
expressions: or a row to constraints: is how a caller extends
the math now, in the same language and with the same load-time checks
the shipped program gets.
Source code in src/fluxopt/flow_system.py
parameters
¶
parameters(profiles: Mapping[str, Any] | None = None) -> Parameters
The numbers this system binds to its math, as data.
The other half of :meth:math. That returns the equations before any
of this system's numbers reach them; this returns the numbers, keyed
as the program declares them — so (math, parameters/) is the
whole problem, hashable and diffable, and a caller adding a constraint
through math= can see what they are adding to.
Derived, never authored: the fold and the pre-scaling have already turned what the user declared into what the solver adds up, so the set is worth keeping and not worth editing. Change the elements or change the program.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Mapping[str, Any] | None
|
Mapping from |
None
|
Source code in src/fluxopt/flow_system.py
build_data
¶
Materialize this declaration's data, resolving profile references.
Resolves ProfileRef references (on a copy — the system stays
reusable across different profiles) and builds the ModelData
both lanes read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Mapping[str, Any] | None
|
Mapping from |
None
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If any |
Source code in src/fluxopt/flow_system.py
optimize
¶
optimize(
profiles: Mapping[str, Any] | None = None,
*,
solver: str = 'highs',
math: Any = None,
dimensions: Mapping[str, Any] | None = None,
lookups: Mapping[str, Any] | None = None,
parameters: Mapping[str, Any] | None = None,
**kwargs: Any,
) -> Result
Resolve profile references, build the data, and solve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Mapping[str, Any] | None
|
Mapping from |
None
|
|
str
|
Solver name — |
'highs'
|
|
Any
|
An edited :meth: |
None
|
|
Mapping[str, Any] | None
|
Labels for dimensions math adds. |
None
|
|
Mapping[str, Any] | None
|
Data for lookups math adds, as |
None
|
|
Mapping[str, Any] | None
|
Data for parameters math adds. The three are the language's own declaration blocks, and
:meth: |
None
|
|
Any
|
Passed to the solver verbatim, in its own vocabulary. |
{}
|