Skip to content

Repository files navigation

fluxopt

Energy system optimization with linopy — detailed dispatch, scaled to multi period planning.

PyPIDownloadsLicense: MITPython 3.12+Ruff

Early development — the API may change between releases. Planned features and progress are tracked in Issues.

Installation

pip install fluxopt

Includes the HiGHS solver out of the box.

Quick Start

# A gas boiler covers a heat demand, minimizing fuel costfromdatetimeimportdatetimefromfluxoptimportCarrier, Converter, Effect, Flow, Port, optimizeresult=optimize(
timesteps=[datetime(2024, 1, 1, h) forhinrange(4)],
carriers=[Carrier(id='gas'), Carrier(id='heat')],
effects=[Effect(id='cost')],
ports=[
Port(id='grid', imports=[Flow(carrier='gas', size=500, effects_per_flow_hour={'cost': 0.04})]),
Port(id='demand', exports=[Flow(carrier='heat', size=100, fixed_relative_profile=[0.4, 0.7, 0.5, 0.6])]),
],
converters=[
Converter.boiler(
'boiler',
thermal_efficiency=0.9,
fuel_flow=Flow(carrier='gas', size=300),
thermal_flow=Flow(carrier='heat', size=200),
)
],
objective='cost',
)
print(f'Total cost: {result.objective:.2f}')
print(result.flow_rates)

One API, four levels of control

Every level returns the same Result; each one only adds control — pick the lowest rung that does the job.

1. One-shotoptimize(...) as above. Elements in, Result out, with fail-fast validation of ids and references.

2. Declarative — gather the same arguments into a reusable, serializable system. Time series can stay out of the structure as ProfileRefs and be supplied at solve time via profiles:

spec=fx.FlowSystem.from_yaml('system.yaml') # or FlowSystem(...) in Pythonresult=spec.optimize(profiles={'load': demand_ds})
spec.to_yaml('system.yaml') # round-trips

3. Inspectable — materialize the solver model without solving, inspect or extend the underlying linopy model, retarget the objective, then solve:

model=spec.build_model(profiles={'load': demand_ds}) # unbuilt FlowSystemModelmodel.build()
model.m.add_constraints(...) # full linopy accessresult=model.solve()
model.objective= {'cost': 1, 'co2': 50} # retarget…model.build() # …and rebuild

For a one-off tweak, stay on level 1/2 and pass customize=lambda m: m.m.add_constraints(...) instead.

4. Data-level — build or load the xarray ModelData yourself and edit it before modeling:

data=fx.ModelData.build(...) # or ModelData.from_netcdf(path)data.flows.fixed_profile.loc[{'flow': 'demand(heat)'}] =0.7result=fx.FlowSystemModel(data, objective='cost').optimize()

Results close the loop: result.flow_rates, result.effect_totals, result.stats (KPIs, effect contributions), result.plot, netCDF round-trip, and result.data — the exact ModelData the solution came from.

Roadmap

fluxopt is evolving into a family of packages with a lean core and optional companions:

 ┌──────────────┐
│ fluxopt │ core: model building, solving, results, IO
└──────┬───────┘
┌──────────────┬─────────┼──────────────┬──────────────┐
│ │ │ │ │
fluxopt-plot fluxopt-yaml fluxopt-tsam fluxopt-marimo (examples)
plotting YAML+CSV time series interactive cross-package
(plotly) loader aggregation apps notebooks

Companion packages depend on core — core has no knowledge of companions.

Companion packages

PackageRoleVersioning · Tierfluxopt pinStatus
fluxopt-plotResult visualization (Plotly)Semver · Experimental — method signatures may changeTight (>=A.B,<A.C), validated per releaseScaffolded — docs · #51
fluxopt-yamlDeclarative model loader (YAML + CSV → Elements)Semver · Experimental — YAML schema may changeTight (>=A.B,<A.C), validated per releaseScaffolded — docs · #52
fluxopt-tsamTime series aggregation — input pre-processing, possibly result disaggregationSemver · Experimental — round-trip schema may evolveUndecided — depends on whether representative-period primitives live in core (→ loose) or in this package (→ tight)Planned
fluxopt-marimoInteractive exploration & dashboards (marimo apps)CalVer (YYYY.MM.PATCH) · Experimental — apps are templatesTight (>=A.B,<A.C), validated per releasePlanned

Tight-pinned companions release on every fluxopt minor; validation is automated via scheduled CI. fluxopt-tsam's pin policy is blocked on an architectural decision — if representative-period primitives live in core, tsam stays a thin adapter (loose pin); if they live in tsam, the package owns deep round-trip behavior (tight pin).

Milestones

Cross-cutting work not tied to a single companion package:

MilestoneDescriptionStatusIssue
Result.stats accessorCached xarray properties for post-processingPlanned#49
.plot stub on ResultDiscoverable property, helpful error if plot package absentPlanned#50
ReadTheDocs migrationAutomatic versioned docs from git tagsPlanned#53
Remove plotly from coreKeep core lean — plotting deps in fluxopt-plot onlyPlanned#54

Stability Tiers

ComponentTierPolicy
Core modeling APIStableSemver. Deprecation warnings before removal.
Stats accessorSemi-stableBreaking changes allowed between minor versions with changelog entry.

Companion packages have their own stability policies — see the table above.

See #47 for the full architecture discussion.

Development

Requires uv and Python >= 3.12.

uv sync --group dev # Install deps
uv run pytest -v # Run tests
uv run ruff check .# Lint
uv run ruff format .# Format

License

MIT

About

Successor of flixopt with a new datamodel

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages