Skip to content

fluxopt.elements

Classes:

Name Description
Element

Base for user-facing element models (shared pydantic config).

Carrier

Physical energy medium (electricity, heat, gas, …).

Sizing

Capacity optimization parameters.

Investment

Singular discrete build-timing optimization.

Status

Binary on/off behavior parameters.

Flow

A single energy flow on a carrier.

Effect

A tracked quantity across the optimization horizon (cost, CO₂, …).

Storage

Energy storage with level dynamics.

PiecewiseConversion

Piecewise-linear conversion linking N flows.

Functions:

Name Description
qualified_id

Format a qualified flow id: component(flow).

node_id

Format a carrier-node id: carrier:node.

Element pydantic-model

Bases: BaseModel

Base for user-facing element models (shared pydantic config).

Show JSON schema:
{
  "description": "Base for user-facing element models (shared pydantic config).",
  "properties": {},
  "title": "Element",
  "type": "object"
}

Carrier pydantic-model

Bases: Element

Physical energy medium (electricity, heat, gas, …).

Show JSON schema:
{
  "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"
}

Fields:

id pydantic-field

id: str

Unique identifier used as xarray coordinate.

nodes pydantic-field

nodes: list[str]

Sub-nodes for multi-node balancing. Empty means single-node.

unit pydantic-field

unit: str = 'MWh'

Energy unit label.

color pydantic-field

color: str | None = None

Optional color for plotting.

description pydantic-field

description: str = ''

Human-readable description.

Sizing pydantic-model

Bases: Element

Capacity optimization parameters.

The solver decides the optimal size within [size_min, size_max].

  • mandatory=True: continuous, size in [min, max], no binary.
  • mandatory=False: binary indicator gates size: 0 or [min, max].
  • size_min == size_max with mandatory=False: binary invest at exact size (yes/no).

See: docs/math/sizing.md

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

Validators:

  • _bounds_are_ordered

size_min pydantic-field

size_min: float

Minimum capacity if invested.

size_max pydantic-field

size_max: float

Maximum capacity.

mandatory pydantic-field

mandatory: bool = True

If True, must be built (no binary indicator).

effects_per_size pydantic-field

effects_per_size: dict[str, Variate]

Effect cost per unit size (e.g. €/MW).

effects_fixed pydantic-field

effects_fixed: dict[str, Variate]

Fixed effect cost if built (optional only).

Investment pydantic-model

Bases: Element

Singular discrete build-timing optimization.

The solver decides WHEN to build (which period) and at what size. Once built, capacity is available for lifetime periods. Size is decided once — no growth or partial retirement.

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

Validators:

  • _bounds_are_ordered

size_min pydantic-field

size_min: float

Minimum capacity if built.

size_max pydantic-field

size_max: float

Maximum capacity.

mandatory pydantic-field

mandatory: bool = True

If True, must build exactly once; if False, may build at most once.

lifetime pydantic-field

lifetime: int | None = None

Periods active after build; None = forever.

prior_size pydantic-field

prior_size: float = 0.0

Pre-existing capacity available from period 0.

effects_per_size_at_build pydantic-field

effects_per_size_at_build: dict[str, Variate]

One-time per-MW costs charged in the build period.

effects_fixed_at_build pydantic-field

effects_fixed_at_build: dict[str, Variate]

One-time fixed costs charged in the build period.

effects_per_size_recurring pydantic-field

effects_per_size_recurring: dict[str, Variate]

Recurring per-MW costs charged every active period.

effects_fixed_recurring pydantic-field

effects_fixed_recurring: dict[str, Variate]

Recurring fixed costs charged every active period.

Status pydantic-model

Bases: Element

Binary on/off behavior parameters.

Together with relative bounds, gives semi-continuous behavior: {0} U [min, max] * size.

See: docs/math/status.md

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

Validators:

  • _durations_are_ordered

uptime_min pydantic-field

uptime_min: float | None = None

Minimum consecutive on-hours.

uptime_max pydantic-field

uptime_max: float | None = None

Maximum consecutive on-hours.

downtime_min pydantic-field

downtime_min: float | None = None

Minimum consecutive off-hours.

downtime_max pydantic-field

downtime_max: float | None = None

Maximum consecutive off-hours.

effects_per_running_hour pydantic-field

effects_per_running_hour: dict[str, Variate]

Effect cost per running hour.

effects_per_startup pydantic-field

effects_per_startup: dict[str, Variate]

Effect cost per startup event.

Flow pydantic-model

Bases: Element

A single energy flow on a carrier.

short_id defaults to carrier (or carrier:node when node is set). Set explicitly to disambiguate multiple flows on the same carrier::

Flow(carrier='elec')  # short_id='elec'
Flow(carrier='heat', node='A')  # short_id='heat:A'
Flow(carrier='elec', short_id='base')  # short_id='base'

short_id must be unique within a component. The system-global qualified id component(short_id) is derived at build time and appears as the flow coordinate in ModelData and results; the flow itself is never modified. Storage resolves colliding short_ids to charge / discharge at that point.

See: docs/math/flows.md

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

carrier pydantic-field

carrier: str

Carrier this flow connects to.

short_id pydantic-field

short_id: str = ''

Component-local identifier; defaults to carrier (or carrier:node). The qualified form component(short_id) is derived at build time.

node pydantic-field

node: str | None = None

Sub-node for multi-node carrier balancing.

size pydantic-field

size: float | Sizing | Investment | None = None

Nominal capacity [MW], Sizing for investment optimization, or None (unsized / unbounded).

relative_rate_min pydantic-field

relative_rate_min: Variate = 0.0

Lower bound as fraction of size.

relative_rate_max pydantic-field

relative_rate_max: Variate = 1.0

Upper bound as fraction of size.

fixed_relative_profile pydantic-field

fixed_relative_profile: Variate | None = None

Fixed profile as fraction of size; sets both lower and upper bounds equal to the profile value.

effects_per_flow_hour pydantic-field

effects_per_flow_hour: dict[str, Variate]

Effect coefficients per flow-hour (e.g. €/MWh).

flow_hours_min pydantic-field

flow_hours_min: float | None = None

Lower bound on total flow-hours Σ P·Δt [MWh], applied to each period independently.

flow_hours_max pydantic-field

flow_hours_max: float | None = None

Upper bound on total flow-hours Σ P·Δt [MWh], applied to each period independently.

load_factor_min pydantic-field

load_factor_min: float | None = None

Lower bound on utilization Σ P·Δt / (size·T), applied to each period independently. Requires size.

load_factor_max pydantic-field

load_factor_max: float | None = None

Upper bound on utilization Σ P·Δt / (size·T), applied to each period independently. Requires size.

ramp_up_per_hour pydantic-field

ramp_up_per_hour: Variate | None = None

Maximum rate increase between consecutive timesteps, as fraction of size per hour. Requires size.

ramp_down_per_hour pydantic-field

ramp_down_per_hour: Variate | None = None

Maximum rate decrease between consecutive timesteps, as fraction of size per hour. Requires size.

status pydantic-field

status: Status | None = None

On/off behavior (semi-continuous, startup costs, durations).

prior_rates pydantic-field

prior_rates: list[float] | None = None

Flow rates [MW] before the horizon, used for status initial conditions.

model_post_init

model_post_init(__context: Any) -> None

Default short_id from carrier/node and validate field combinations.

Source code in src/fluxopt/elements.py
@override
def model_post_init(self, __context: Any) -> None:
    """Default short_id from carrier/node and validate field combinations."""
    if not self.short_id:
        self.short_id = node_id(self.carrier, self.node) if self.node else self.carrier
    if self.status is not None and isinstance(self.relative_rate_min, (int, float)) and self.relative_rate_min <= 0:
        msg = (
            f'Flow {self.short_id!r}: relative_rate_min must be > 0 when status is set, '
            f'otherwise on/off is indistinguishable (got {self.relative_rate_min})'
        )
        raise ValueError(msg)
    if (
        self.status is not None
        and self.fixed_relative_profile is not None
        and isinstance(self.size, (Sizing, Investment))
    ):
        msg = (
            f'Flow {self.short_id!r}: fixed_relative_profile is not supported together with '
            f'Status and Sizing/Investment — fix the size or drop the profile'
        )
        raise ValueError(msg)
    if (self.load_factor_min is not None or self.load_factor_max is not None) and self.size is None:
        msg = (
            f'Flow {self.short_id!r}: load_factor bounds require a size '
            f'(fixed or Sizing/Investment) — the load factor is relative to size'
        )
        raise ValueError(msg)
    if (self.ramp_up_per_hour is not None or self.ramp_down_per_hour is not None) and self.size is None:
        msg = (
            f'Flow {self.short_id!r}: ramp limits require a size '
            f'(fixed or Sizing/Investment) — ramps are relative to size'
        )
        raise ValueError(msg)

Effect pydantic-model

Bases: Element

A tracked quantity across the optimization horizon (cost, CO₂, …).

One effect is designated as the objective to minimize via the objective argument of optimize(). Others can be bounded to enforce budgets (e.g. emission caps).

Effects accumulate contributions from two domains:

  • Temporal — per-timestep flow costs, running costs, startup costs.
  • Lump — one-time sizing costs and fixed costs.

Cross-effect chains (e.g. CO₂ → cost) are supported via contribution_from.

See: docs/math/effects.md

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

id pydantic-field

id: str

Unique identifier.

unit pydantic-field

unit: str = ''

Unit label (e.g. '€', 'kg').

total_max pydantic-field

total_max: float | None = None

Upper bound on weighted total across all periods.

total_min pydantic-field

total_min: float | None = None

Lower bound on weighted total across all periods.

periodic_max pydantic-field

periodic_max: Variate | None = None

Upper bound applied to each period independently. Scalar or per-period values (multi-period only).

periodic_min pydantic-field

periodic_min: Variate | None = None

Lower bound applied to each period independently. Scalar or per-period values (multi-period only).

contribution_from pydantic-field

contribution_from: dict[str, Variate]

Cross-effect factors {source_effect: factor}. Scalar factors apply identically to both domains. Time-varying factors apply per-timestep in the temporal domain and are rejected at build time when the source effect carries lump (sizing/fixed) contributions.

period_weights pydantic-field

period_weights: list[float] | None = None

Per-period weights ω for total aggregation; overrides global period_weights.

Storage pydantic-model

Bases: Element

Energy storage with level dynamics.

Flow ids are qualified as storage(flow) at build time. When both flows share a short_id, the qualified ids use charge / discharge instead (the flows themselves are never modified)::

Storage(
    id='bat', charging=Flow(carrier='elec'), discharging=Flow(carrier='elec')
)  # bat(charge), bat(discharge)
Storage(
    id='bat',
    charging=Flow(carrier='elec', short_id='in'),
    discharging=Flow(carrier='elec', short_id='out'),
)  # bat(in), bat(out)

Level balance::

E_{s,t+1} = E_{s,t} (1 - δ)^Δt + P^c η^c Δt - P^d / η^d Δt

See: docs/math/storage.md

Show JSON schema:
{
  "$defs": {
    "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"
    },
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

id pydantic-field

id: str

Storage identifier.

charging pydantic-field

charging: Flow

Charging flow.

discharging pydantic-field

discharging: Flow

Discharging flow.

capacity pydantic-field

capacity: float | Sizing | Investment | None = None

Maximum stored energy [MWh], Sizing for investment optimization, or None.

eta_charge pydantic-field

eta_charge: Variate = 1.0

Charging efficiency.

eta_discharge pydantic-field

eta_discharge: Variate = 1.0

Discharging efficiency.

relative_loss_per_hour pydantic-field

relative_loss_per_hour: Variate = 0.0

Self-discharge rate [1/h].

prior_level pydantic-field

prior_level: float | None = None

Initial energy level [MWh]; None = unconstrained.

cyclic pydantic-field

cyclic: bool = True

If True, end level must equal start level.

relative_level_min pydantic-field

relative_level_min: Variate = 0.0

Min SOC as fraction of capacity.

relative_level_max pydantic-field

relative_level_max: Variate = 1.0

Max SOC as fraction of capacity.

final_level_min pydantic-field

final_level_min: float | None = None

Lower bound on the level at the last timestep [MWh], per period; None = unconstrained.

final_level_max pydantic-field

final_level_max: float | None = None

Upper bound on the level at the last timestep [MWh], per period; None = unconstrained.

prevent_simultaneous pydantic-field

prevent_simultaneous: bool = False

If True, a binary per timestep excludes charging and discharging at once. Requires sized flows (fixed or Sizing/Investment) for the big-M bound.

status pydantic-field

status: Status | None = None

Component-level on/off behavior gating both charging and discharging. Forbids flow-level status on the child flows (the two switches would have no defined precedence).

model_post_init

model_post_init(__context: Any) -> None

Validate physical ranges, carrier match, and status/size requirements.

Source code in src/fluxopt/elements.py
@override
def model_post_init(self, __context: Any) -> None:
    """Validate physical ranges, carrier match, and status/size requirements."""
    if isinstance(self.capacity, (int, float)) and self.capacity < 0:
        msg = f'Storage {self.id!r}: capacity is negative ({self.capacity})'
        raise ValueError(msg)
    for name, low, high, low_open in (
        ('eta_charge', 0.0, 1.0, True),
        ('eta_discharge', 0.0, 1.0, True),
        ('relative_loss_per_hour', 0.0, 1.0, False),
    ):
        bad = variate_out_of_range(getattr(self, name), low=low, high=high, low_open=low_open)
        if bad is not None:
            span = f'({low}, {high}]' if low_open else f'[{low}, {high}]'
            msg = f'Storage {self.id!r}: {name} must be in {span}, got {bad}'
            raise ValueError(msg)
    if self.charging.carrier != self.discharging.carrier:
        msg = (
            f'Storage {self.id!r}: charging carrier {self.charging.carrier!r} '
            f'!= discharging carrier {self.discharging.carrier!r}'
        )
        raise ValueError(msg)
    if self.status is not None:
        for f in (self.charging, self.discharging):
            if f.status is not None:
                msg = (
                    f'Storage {self.id!r}: flow {f.short_id!r} cannot have flow-level '
                    f'status when Storage.status is set; the component status already gates both flows'
                )
                raise ValueError(msg)
            if f.size is None:
                msg = (
                    f'Storage {self.id!r}: flow {f.short_id!r} must have a size when '
                    f'Storage.status is set — without it, the on/off binary cannot gate '
                    f'the rate (no upper bound to scale)'
                )
                raise ValueError(msg)
    if self.prevent_simultaneous:
        for f in (self.charging, self.discharging):
            if f.size is None:
                msg = (
                    f'Storage {self.id!r}: flow {f.short_id!r} must have a size when '
                    f'prevent_simultaneous is set — the exclusion binary needs an '
                    f'upper bound on the rate (big-M)'
                )
                raise ValueError(msg)

PiecewiseConversion pydantic-model

Bases: Element

Piecewise-linear conversion linking N flows.

Wraps :func:linopy.piecewise.add_piecewise_formulation. All flows share interpolation weights — every operating point lies on the same piece of the curve.

Two input forms:

  • Dict — equality-only, terse for the common case::

    PiecewiseConversion(points={'fuel': [0, 50, 100], 'Heat': [0, 45, 70]})

  • List of tuples — supports per-flow inequality bounds::

    PiecewiseConversion( points=[ ('fuel', [0, 50, 100]), ('Heat', [0, 45, 70], '>='), ] )

See: docs/math/converters.md

Show JSON schema:
{
  "$defs": {
    "PiecewiseMethod": {
      "enum": [
        "auto",
        "sos2",
        "incremental",
        "lp"
      ],
      "type": "string"
    },
    "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"
    },
    "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"
    },
    "Variate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "type": "number"
          },
          "type": "array"
        },
        {
          "$ref": "#/$defs/ProfileRef"
        }
      ]
    }
  },
  "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"
}

Fields:

points pydantic-field

points: dict[str, list[Variate]] | list[_CurveTuple]

Per-flow breakpoints. Either {flow: [bp...]} (equality only) or a list of (flow, [bp...]) / (flow, [bp...], '<='|'>=') tuples. Need >=2 flows; all breakpoint lists must share the same length (>=2). At most one tuple may carry a non-equality bound, and only when exactly two flows are present.

method pydantic-field

method: PiecewiseMethod = 'auto'

Formulation. "auto" picks LP (2 flows + bounded + matching convexity), else incremental (monotonic) or sos2. Override with "sos2" / "incremental" / "lp".

status pydantic-field

status: Status | None = None

Component-level on/off behavior gating the curve.

availability pydantic-field

availability: Variate = 1.0

Time-varying scaling of the upper breakpoint.

model_post_init

model_post_init(__context: Any) -> None

Validate normalized breakpoints and bound combinations.

Source code in src/fluxopt/elements.py
@override
def model_post_init(self, __context: Any) -> None:
    """Validate normalized breakpoints and bound combinations."""
    flows_pts_bounds = list(self._iter_normalized())

    if len(flows_pts_bounds) < 2:
        msg = f'PiecewiseConversion needs >=2 flows, got {len(flows_pts_bounds)}'
        raise ValueError(msg)

    n = len(flows_pts_bounds[0][1])
    if n < 2:
        msg = f'PiecewiseConversion needs >=2 breakpoints per flow, got {n}'
        raise ValueError(msg)

    if any(len(pts) != n for _, pts, _ in flows_pts_bounds):
        lengths = {flow: len(pts) for flow, pts, _ in flows_pts_bounds}
        msg = f'PiecewiseConversion breakpoint lists must all have the same length, got {lengths}'
        raise ValueError(msg)

    flows = [flow for flow, _, _ in flows_pts_bounds]
    if len(set(flows)) != len(flows):
        dupes = [f for f in flows if flows.count(f) > 1]
        msg = f'PiecewiseConversion has duplicate flow ids: {sorted(set(dupes))}'
        raise ValueError(msg)

    nonequal = [b for _, _, b in flows_pts_bounds if b != '==']
    if len(nonequal) > 1:
        msg = f'At most one bounded flow per PiecewiseConversion, got {len(nonequal)}'
        raise ValueError(msg)
    if nonequal and len(flows_pts_bounds) > 2:
        msg = f'Inequality bounds require exactly 2 flows, got {len(flows_pts_bounds)}'
        raise ValueError(msg)
    if self.method == 'lp' and not nonequal:
        msg = "method='lp' requires one flow with bound '<=' or '>='"
        raise ValueError(msg)

qualified_id

qualified_id(component: str, flow: str) -> str

Format a qualified flow id: component(flow).

Source code in src/fluxopt/elements.py
def qualified_id(component: str, flow: str) -> str:
    """Format a qualified flow id: ``component(flow)``."""
    return QUAL_FMT.format(component=component, flow=flow)

node_id

node_id(carrier: str, node: str) -> str

Format a carrier-node id: carrier:node.

Source code in src/fluxopt/elements.py
def node_id(carrier: str, node: str) -> str:
    """Format a carrier-node id: ``carrier:node``."""
    return f'{carrier}{NODE_SEP}{node}'