Quickstart¶
The minimum viable fluxopt model: a gas boiler covers a 4-hour workshop heat demand.
Gas Grid ──[gas]──► Boiler (η=0.9) ──[heat]──► Workshop
0.08 €/kWh 50 kW peak
You'll meet the five pieces every fluxopt model needs: Carrier, Flow, Port, Converter, Effect — and the entry point: optimize().
from datetime import datetime
from fluxopt import Carrier, Converter, Effect, Flow, Port, optimize
1. Declare the system¶
A fluxopt model is built from these pieces:
- A
Carrieris an energy type —gas,heat,electricity, … - A
Flowis energy moving on a carrier (in MW). Flows live onPorts (system boundaries: imports/exports) andConverters (couple input flows to output flows). - A
Converterturns input flows into output flows via a conversion equation.Converter.boiler(...)is a shortcut for the standard relationfuel · η = heat. - An
Effectis anything you want to track or minimize: cost, CO₂, fuel use. EachFlowdeclares which effects it contributes to viaeffects_per_flow_hour.
carriers = [Carrier('gas'), Carrier('heat')]
effects = [Effect('cost', unit='EUR')]
ports = [
# Gas comes in from the grid, costs 0.08 EUR per kWh delivered.
Port(
'gas_grid',
imports=[
Flow('gas', size=1000, effects_per_flow_hour={'cost': 0.08}),
],
),
# The workshop's heat demand: a fixed profile, scaled to 50 kW peak.
Port(
'workshop',
exports=[
Flow('heat', size=50, fixed_relative_profile=[0.6, 1.0, 0.9, 0.5]),
],
),
]
converters = [
Converter.boiler(
'boiler',
thermal_efficiency=0.9,
fuel_flow=Flow('gas', size=200),
thermal_flow=Flow('heat', size=100),
),
]
2. Solve¶
optimize() builds the linear program, hands it to HiGHS, and returns a Result object. We minimize 'cost' — the sum of cost contributions from every flow.
result = optimize(
timesteps=[datetime(2024, 1, 15, h) for h in range(8, 12)],
carriers=carriers,
effects=effects,
ports=ports,
converters=converters,
objective_effects='cost',
)
Running HiGHS 1.13.1 (git hash: 1d267d9): Copyright (c) 2026 under MIT licence terms LP linopy-problem-xp1yan20 has 52 rows; 28 cols; 78 nonzeros Coefficient ranges: Matrix [8e-02, 1e+00] Cost [1e+00, 1e+00] Bound [0e+00, 0e+00] RHS [2e+01, 1e+03] Presolving model 0 rows, 0 cols, 0 nonzeros 0s 0 rows, 0 cols, 0 nonzeros 0s Presolve reductions: rows 0(-52); columns 0(-28); nonzeros 0(-78) - Reduced to empty Performed postsolve Solving the original LP from the solution after postsolve Model name : linopy-problem-xp1yan20 Model status : Optimal Objective value : 1.3333333333e+01 P-D objective error : 1.2841133779e-16 HiGHS run time : 0.00
3. Read the result¶
The Result exposes solver outputs as xarray arrays. The most-used:
result.objective— the minimized total (here, total cost in EUR)result.effect_totals— totals per tracked effect (1-D,effect)result.flow_rates— every flow's rate per timestep (2-D,flow × time, in MW)
print(f'Total cost: {result.objective:.2f} EUR')
result.effect_totals.drop_sel(effect='penalty').to_dataframe(name='total')
Total cost: 13.33 EUR
| total | |
|---|---|
| effect | |
| cost | 13.333333 |
All flow rates per timestep — notice how the boiler's gas input equals the heat output divided by 0.9, the conversion equation acting per row.
result.flow_rates.to_pandas().T.round(2)
| flow | gas_grid(gas) | workshop(heat) | boiler(gas) | boiler(heat) |
|---|---|---|---|---|
| time | ||||
| 2024-01-15 08:00:00 | 33.33 | 30.0 | 33.33 | 30.0 |
| 2024-01-15 09:00:00 | 55.56 | 50.0 | 55.56 | 50.0 |
| 2024-01-15 10:00:00 | 50.00 | 45.0 | 50.00 | 45.0 |
| 2024-01-15 11:00:00 | 27.78 | 25.0 | 27.78 | 25.0 |
Recap¶
You declared five kinds of object — Carrier, Flow, Port, Converter, Effect — passed them to optimize(), and read the solution as xarray.
Note flow ids: by default a flow's id is qualified as component(carrier) — boiler(gas), workshop(heat). That keeps flow names unique even when several flows share a carrier.
Next: Storage — adding a thermal storage to the same workshop.