Energy system models from components: fluxopt writes them into tables for its math, which mathspec states and specsolve solves — detailed dispatch, scaled to multi-period planning.
Early development — the API may change between releases. Planned features and progress are tracked in Issues.
- Composable elements. Build models from
Carrier,Flow,Port,Converter,StorageandEffect— clear separation of physics, costs, and topology. API → - Math as a file. The math is YAML that mathspec checks and prints, the numbers are polars tables, and specsolve solves the two together. How it works →
- Sizing & status. Capacity optimization and on/off behavior as first-class concerns, not bolt-ons. Sizing →
- HiGHS out of the box. Open-source MIP solver bundled. Swap in another
solver with
solver=. Quickstart → - Math, documented. Every feature has a page that explains its formulation, and a page that prints the exact math the solver reads. Notation →
- Companion ecosystem. Lean core, optional companions for plotting, YAML loading, and (planned) interactive marimo apps. Roadmap →
pip install fluxoptIncludes the HiGHS solver out of the box.
# A gas boiler covers a heat demand, minimizing fuel cost
from datetime import datetime
from fluxopt import Carrier, Converter, Effect, Flow, Port, optimize
result = optimize(
timesteps=[datetime(2024, 1, 1, h) for h in range(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.primal('rate'))Every level answers with specsolve's Result; each one only adds control —
pick the lowest rung that does the job.
1. One-shot — optimize(...) as above. Elements in, a solved answer 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, one polars table per ProfileRef.table:
system = fx.FlowSystem.from_yaml('system.yaml') # or FlowSystem(...) in Python
load = pl.read_csv('load.csv', try_parse_dates=True) # columns: time, demand
result = system.optimize(profiles={'load': load}, archive='run.zip')
system.to_yaml('system.yaml') # round-trips3. Spec and sources — the system is a mathspec spec and the tables bound to it. Read, typeset or extend the spec, edit any table, and solve with specsolve:
import mathspec, specsolve
spec = mathspec.override(system.spec(), ['my_cap.yaml'])
sources = system.sources(profiles={'load': load}) | {'grid_cap': caps}
result = specsolve.solve(spec, sources)The spec's assumptions: check whatever tables arrive.
Read an answer as a tidy polars table: result.primal("rate") for a variable,
or result.evaluate("flow_hours") for a reported quantity:
flow hours, carrier balance, capacity factor, storage mean level, and each
contribution with its cross-effects charged (priced_*). archive= writes
the spec, its sources and the answer; specsolve.load_archive reads them back.
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.
| Package | Role | Versioning · Tier | fluxopt pin |
Status |
|---|---|---|---|---|
fluxopt-plot |
Result visualization (Plotly) | Semver · Experimental — method signatures may change | Tight (>=A.B,<A.C), validated per release |
Scaffolded — docs · #51 |
fluxopt-yaml |
Declarative model loader (YAML + CSV → Elements) |
Semver · Experimental — YAML schema may change | Tight (>=A.B,<A.C), validated per release |
Scaffolded — docs · #52 |
fluxopt-tsam |
Time series aggregation — input pre-processing, possibly result disaggregation | Semver · Experimental — round-trip schema may evolve | Undecided — depends on whether representative-period primitives live in core (→ loose) or in this package (→ tight) | Planned |
fluxopt-marimo |
Interactive exploration & dashboards (marimo apps) | CalVer (YYYY.MM.PATCH) · Experimental — apps are templates |
Tight (>=A.B,<A.C), validated per release |
Planned |
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).
Cross-cutting work not tied to a single companion package:
| Milestone | Description | Status | Issue |
|---|---|---|---|
| ReadTheDocs migration | Automatic versioned docs from git tags | Planned | #53 |
| Remove plotly from core | Keep core lean — plotting deps in fluxopt-plot only |
Planned | #54 |
| Component | Tier | Policy |
|---|---|---|
| Core modeling API | Stable | Semver. Deprecation warnings before removal. |
| Stats accessor | Semi-stable | Breaking 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.
Requires pixi. It installs Python and every tool from
pixi.lock.
pixi run pre-commit-install # Lint every commit
pixi run test # Run tests
pixi run lint # Lint, format and type-check
pixi run ci # Everything CI checksSee CONTRIBUTING and RELEASING.
MIT