Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
197038b
⏺ Everything works correctly. The dataclass conversion is complete:
FBumann Feb 15, 2026
80226bd
refactor: move leaf interface alignment from Elements to *Data classes
FBumann Feb 15, 2026
51e92eb
refactor: remove dead transform_data/link_to_flow_system from leaf in…
FBumann Feb 15, 2026
5174573
refactor: move Flow.transform_data alignment into FlowsData
FBumann Feb 15, 2026
81d2ef2
refactor: decouple leaf dataclasses from Interface
FBumann Feb 15, 2026
8b02255
refactor: move Effect.transform_data alignment into EffectsData
FBumann Feb 15, 2026
f8265a5
refactor: move Bus.transform_data alignment into BusesData
FBumann Feb 15, 2026
19f258f
refactor: move LinearConverter.transform_data alignment into Converte…
FBumann Feb 15, 2026
4def21b
refactor: move Storage.transform_data alignment into StoragesData
FBumann Feb 15, 2026
435ffa5
refactor: move Transmission.transform_data alignment into Transmissio…
FBumann Feb 15, 2026
8c7cd22
refactor: remove transform_data loop, extract status propagation
FBumann Feb 15, 2026
4859471
refactor: consolidate validate_config into *Data.validate()
FBumann Feb 15, 2026
96bee1f
refactor: extract IO from Interface into standalone functions with pa…
FBumann Feb 15, 2026
447cfea
refactor: extract utilities from Interface, remove Carrier inheritance
FBumann Feb 15, 2026
32f9fce
Remove prefixing from Interface
FBumann Feb 15, 2026
9e710fb
refactor: remove link_to_flow_system(), set _flow_system directly
FBumann Feb 15, 2026
e51f6c5
refactor: convert element classes to @dataclass, remove Interface
FBumann Feb 15, 2026
d0a5990
refactor: simplify Flow __init__ and remove Element.__init__
FBumann Feb 15, 2026
b1d68ed
refactor: strip Element to thin mixin, remove unused IO methods
FBumann Feb 15, 2026
6be43d9
refactor: coords-aware serialization, remove premature DataArray conv…
FBumann Feb 16, 2026
9f0dfdd
refactor: clean up numeric array serialization, improve naming
FBumann Feb 16, 2026
9059222
refactor: separate user API from internal runtime state
FBumann Feb 16, 2026
3b7095b
refactor: simplify Flow API — remove unnecessary flow_id, auto-defaul…
FBumann Feb 16, 2026
c3cb15e
refactor: drop @dataclass from Storage and Transmission, use manual _…
FBumann Feb 16, 2026
09c1e7f
Fix non default fields
FBumann Feb 16, 2026
e85b659
refactor: make Component.id and Flow.bus required, simplify inputs/ou…
FBumann Feb 16, 2026
1329ce6
refactor: store all numerics as DataArrays in dataset, use flow ids i…
FBumann Feb 16, 2026
757695e
Use | as delimiter in io
FBumann Feb 16, 2026
f47ef9d
Use | as delimiter in io
FBumann Feb 16, 2026
0eea0b0
refactor: decouple FlowsData from FlowSystem reference
FBumann Feb 16, 2026
09d5ef7
refactor: replace FlowsData lazy properties with eager xr.Dataset bui…
FBumann Feb 16, 2026
27c1f37
refactor: remove FlowsData pass-through properties, access ds directly
FBumann Feb 16, 2026
2f667fa
refactor: flatten component hierarchy with self-contained Converter, …
FBumann Feb 16, 2026
002a483
refactor: add generic add(), self-contained Transmission, simplified …
FBumann Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp

- **Penalty as first-class Effect**: Users can now add Penalty contributions anywhere effects are used:
```python
fx.Flow('Q', 'Bus', effects_per_flow_hour={'Penalty': 2.5})
fx.Flow(bus='Bus', flow_id='Q', effects_per_flow_hour={'Penalty': 2.5})
fx.InvestParameters(..., effects_of_investment={'Penalty': 100})
```
- **User-definable Penalty**: Optionally define custom Penalty with constraints (auto-created if not defined):
Expand Down
35 changes: 20 additions & 15 deletions benchmarks/benchmark_model_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def create_large_system(
fs.add_elements(
fx.Source(
'GasGrid',
outputs=[fx.Flow('Gas', bus='Gas', size=5000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.2})],
outputs=[fx.Flow(bus='Gas', size=5000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.2})],
)
)

Expand All @@ -255,19 +255,25 @@ def create_large_system(
fx.Source(
'ElecBuy',
outputs=[
fx.Flow('El', bus='Electricity', size=2000, effects_per_flow_hour={'costs': elec_price, 'CO2': 0.4})
fx.Flow(
bus='Electricity', flow_id='El', size=2000, effects_per_flow_hour={'costs': elec_price, 'CO2': 0.4}
)
],
),
fx.Sink(
'ElecSell',
inputs=[fx.Flow('El', bus='Electricity', size=1000, effects_per_flow_hour={'costs': -elec_price * 0.8})],
inputs=[
fx.Flow(bus='Electricity', flow_id='El', size=1000, effects_per_flow_hour={'costs': -elec_price * 0.8})
],
),
)

# Demands
fs.add_elements(
fx.Sink('HeatDemand', inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_profile)]),
fx.Sink('ElecDemand', inputs=[fx.Flow('El', bus='Electricity', size=1, fixed_relative_profile=elec_profile)]),
fx.Sink('HeatDemand', inputs=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=heat_profile)]),
fx.Sink(
'ElecDemand', inputs=[fx.Flow(bus='Electricity', flow_id='El', size=1, fixed_relative_profile=elec_profile)]
),
)

# Converters (CHPs and Boilers)
Expand All @@ -294,10 +300,10 @@ def create_large_system(
fs.add_elements(
fx.LinearConverter(
f'CHP_{i}',
inputs=[fx.Flow('Gas', bus='Gas', size=300)],
inputs=[fx.Flow(bus='Gas', size=300)],
outputs=[
fx.Flow('El', bus='Electricity', size=100),
fx.Flow('Heat', bus='Heat', size=size_param, status_parameters=status_param),
fx.Flow(bus='Electricity', flow_id='El', size=100),
fx.Flow(bus='Heat', size=size_param, status_parameters=status_param),
],
piecewise_conversion=fx.PiecewiseConversion(
{
Expand All @@ -314,9 +320,9 @@ def create_large_system(
f'CHP_{i}',
thermal_efficiency=0.50,
electrical_efficiency=0.35,
thermal_flow=fx.Flow('Heat', bus='Heat', size=size_param, status_parameters=status_param),
electrical_flow=fx.Flow('El', bus='Electricity', size=100),
fuel_flow=fx.Flow('Gas', bus='Gas'),
thermal_flow=fx.Flow(bus='Heat', size=size_param, status_parameters=status_param),
electrical_flow=fx.Flow(bus='Electricity', flow_id='El', size=100),
fuel_flow=fx.Flow(bus='Gas'),
)
)
else:
Expand All @@ -326,13 +332,12 @@ def create_large_system(
f'Boiler_{i}',
thermal_efficiency=0.90,
thermal_flow=fx.Flow(
'Heat',
bus='Heat',
size=size_param,
relative_minimum=0.2,
status_parameters=status_param,
),
fuel_flow=fx.Flow('Gas', bus='Gas'),
fuel_flow=fx.Flow(bus='Gas'),
)
)

Expand All @@ -356,8 +361,8 @@ def create_large_system(
eta_charge=0.95,
eta_discharge=0.95,
relative_loss_per_hour=0.001,
charging=fx.Flow('Charge', bus='Heat', size=100),
discharging=fx.Flow('Discharge', bus='Heat', size=100),
charging=fx.Flow(bus='Heat', size=100),
discharging=fx.Flow(bus='Heat', size=100),
)
)

Expand Down
14 changes: 6 additions & 8 deletions docs/home/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ solar_profile = np.array([0, 0, 0, 0, 0, 0, 0.2, 0.5, 0.8, 1.0,
1.0, 0.9, 0.8, 0.7, 0.5, 0.3, 0.1, 0,
0, 0, 0, 0, 0, 0])

solar = fx.Source(
solar = fx.Port(
'solar',
outputs=[fx.Flow(
'power',
imports=[fx.Flow(
bus='electricity',
size=100, # 100 kW capacity
relative_maximum=solar_profile
Expand All @@ -66,18 +65,17 @@ demand_profile = np.array([30, 25, 20, 20, 25, 35, 50, 70, 80, 75,
70, 65, 60, 65, 70, 80, 90, 95, 85, 70,
60, 50, 40, 35])

demand = fx.Sink('demand', inputs=[
fx.Flow('consumption',
bus='electricity',
demand = fx.Port('demand', exports=[
fx.Flow(bus='electricity',
size=1,
fixed_relative_profile=demand_profile)
])

# Battery storage
battery = fx.Storage(
'battery',
charging=fx.Flow('charge', bus='electricity', size=50),
discharging=fx.Flow('discharge', bus='electricity', size=50),
charging=fx.Flow(bus='electricity', size=50),
discharging=fx.Flow(bus='electricity', size=50),
capacity_in_flow_hours=100, # 100 kWh capacity
initial_charge_state=50, # Start at 50%
eta_charge=0.95,
Expand Down
16 changes: 8 additions & 8 deletions docs/notebooks/01-quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"- **FlowSystem**: The container for your energy system model\n",
"- **Bus**: Balance nodes where energy flows meet\n",
"- **Effect**: Quantities to track and optimize (costs, emissions)\n",
"- **Components**: Equipment like boilers, sources, and sinks\n",
"- **Components**: Equipment like boilers and ports\n",
"- **Flow**: Connections between components and buses"
]
},
Expand Down Expand Up @@ -125,21 +125,21 @@
" # === Effect: What we want to minimize ===\n",
" fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
" # === Gas Supply: Unlimited gas at 0.08 €/kWh ===\n",
" fx.Source(\n",
" fx.Port(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.08)],\n",
" imports=[fx.Flow(bus='Gas', size=1000, effects_per_flow_hour=0.08)],\n",
" ),\n",
" # === Boiler: Converts gas to heat at 90% efficiency ===\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'Boiler',\n",
" thermal_efficiency=0.9,\n",
" thermal_flow=fx.Flow('Heat', bus='Heat', size=100), # 100 kW capacity\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Heat', size=100), # 100 kW capacity\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" # === Workshop: Heat demand that must be met ===\n",
" fx.Sink(\n",
" fx.Port(\n",
" 'Workshop',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],\n",
" exports=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],\n",
" ),\n",
")"
]
Expand Down
18 changes: 9 additions & 9 deletions docs/notebooks/02-heat-system.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,16 @@
" # === Effect ===\n",
" fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
" # === Gas Supply with time-varying price ===\n",
" fx.Source(\n",
" fx.Port(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=gas_price)],\n",
" imports=[fx.Flow(bus='Gas', size=500, effects_per_flow_hour=gas_price)],\n",
" ),\n",
" # === Gas Boiler: 150 kW, 92% efficiency ===\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'Boiler',\n",
" thermal_efficiency=0.92,\n",
" thermal_flow=fx.Flow('Heat', bus='Heat', size=150),\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Heat', size=150),\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" # === Thermal Storage: 500 kWh tank ===\n",
" fx.Storage(\n",
Expand All @@ -166,13 +166,13 @@
" eta_charge=0.98, # 98% charging efficiency\n",
" eta_discharge=0.98, # 98% discharging efficiency\n",
" relative_loss_per_hour=0.005, # 0.5% heat loss per hour\n",
" charging=fx.Flow('Charge', bus='Heat', size=100), # Max 100 kW charging\n",
" discharging=fx.Flow('Discharge', bus='Heat', size=100), # Max 100 kW discharging\n",
" charging=fx.Flow(bus='Heat', size=100), # Max 100 kW charging\n",
" discharging=fx.Flow(bus='Heat', size=100), # Max 100 kW discharging\n",
" ),\n",
" # === Office Heat Demand ===\n",
" fx.Sink(\n",
" fx.Port(\n",
" 'Office',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n",
" exports=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n",
" ),\n",
")"
]
Expand Down
25 changes: 12 additions & 13 deletions docs/notebooks/03-investment-optimization.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,22 @@
" # === Effects ===\n",
" fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
" # === Gas Supply ===\n",
" fx.Source(\n",
" fx.Port(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=GAS_PRICE)],\n",
" imports=[fx.Flow(bus='Gas', size=500, effects_per_flow_hour=GAS_PRICE)],\n",
" ),\n",
" # === Gas Boiler (existing, fixed size) ===\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'GasBoiler',\n",
" thermal_efficiency=0.92,\n",
" thermal_flow=fx.Flow('Heat', bus='Heat', size=200), # 200 kW existing\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Heat', size=200), # 200 kW existing\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" # === Solar Collectors (size to be optimized) ===\n",
" fx.Source(\n",
" fx.Port(\n",
" 'SolarCollectors',\n",
" outputs=[\n",
" imports=[\n",
" fx.Flow(\n",
" 'Heat',\n",
" bus='Heat',\n",
" # Investment optimization: find optimal size between 0-500 kW\n",
" size=fx.InvestParameters(\n",
Expand All @@ -181,13 +180,13 @@
" eta_charge=0.95,\n",
" eta_discharge=0.95,\n",
" relative_loss_per_hour=0.01, # 1% loss per hour\n",
" charging=fx.Flow('Charge', bus='Heat', size=200),\n",
" discharging=fx.Flow('Discharge', bus='Heat', size=200),\n",
" charging=fx.Flow(bus='Heat', size=200),\n",
" discharging=fx.Flow(bus='Heat', size=200),\n",
" ),\n",
" # === Pool Heat Demand ===\n",
" fx.Sink(\n",
" fx.Port(\n",
" 'Pool',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n",
" exports=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n",
" ),\n",
")"
]
Expand Down Expand Up @@ -404,7 +403,7 @@
"\n",
"### Where to Use InvestParameters\n",
"\n",
"- **Flow.size**: Optimize converter/source/sink capacity\n",
"- **Flow.size**: Optimize converter/port capacity\n",
"- **Storage.capacity_in_flow_hours**: Optimize storage capacity\n",
"\n",
"## Summary\n",
Expand Down
36 changes: 18 additions & 18 deletions docs/notebooks/04-operational-constraints.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@
" # === Effect ===\n",
" fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
" # === Gas Supply ===\n",
" fx.Source(\n",
" fx.Port(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)],\n",
" imports=[fx.Flow(bus='Gas', size=1000, effects_per_flow_hour=0.06)],\n",
" ),\n",
" # === Main Industrial Boiler (with operational constraints) ===\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'MainBoiler',\n",
" thermal_efficiency=0.94, # High efficiency\n",
" # StatusParameters define on/off behavior\n",
Expand All @@ -144,20 +144,20 @@
" size=500,\n",
" relative_minimum=0.3, # Minimum load: 30% = 150 kW\n",
" ),\n",
" fuel_flow=fx.Flow('Gas', bus='Gas', size=600), # Size required for status_parameters\n",
" fuel_flow=fx.Flow(bus='Gas', size=600), # Size required for status_parameters\n",
" ),\n",
" # === Backup Boiler (flexible, but less efficient) ===\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'BackupBoiler',\n",
" thermal_efficiency=0.85, # Lower efficiency\n",
" # No status parameters = can turn on/off freely\n",
" thermal_flow=fx.Flow('Steam', bus='Steam', size=150),\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Steam', size=150),\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" # === Factory Steam Demand ===\n",
" fx.Sink(\n",
" fx.Port(\n",
" 'Factory',\n",
" inputs=[fx.Flow('Steam', bus='Steam', size=1, fixed_relative_profile=steam_demand)],\n",
" exports=[fx.Flow(bus='Steam', size=1, fixed_relative_profile=steam_demand)],\n",
" ),\n",
")"
]
Expand Down Expand Up @@ -340,21 +340,21 @@
" fx.Bus('Gas', carrier='gas'),\n",
" fx.Bus('Steam', carrier='steam'),\n",
" fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
" fx.Source('GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]),\n",
" fx.Port('GasGrid', imports=[fx.Flow(bus='Gas', size=1000, effects_per_flow_hour=0.06)]),\n",
" # Main boiler WITHOUT status parameters\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'MainBoiler',\n",
" thermal_efficiency=0.94,\n",
" thermal_flow=fx.Flow('Steam', bus='Steam', size=500),\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Steam', size=500),\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" fx.linear_converters.Boiler(\n",
" fx.Converter.boiler(\n",
" 'BackupBoiler',\n",
" thermal_efficiency=0.85,\n",
" thermal_flow=fx.Flow('Steam', bus='Steam', size=150),\n",
" fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
" thermal_flow=fx.Flow(bus='Steam', size=150),\n",
" fuel_flow=fx.Flow(bus='Gas'),\n",
" ),\n",
" fx.Sink('Factory', inputs=[fx.Flow('Steam', bus='Steam', size=1, fixed_relative_profile=steam_demand)]),\n",
" fx.Port('Factory', exports=[fx.Flow(bus='Steam', size=1, fixed_relative_profile=steam_demand)]),\n",
")\n",
"\n",
"fs_unconstrained.optimize(fx.solvers.HighsSolver())\n",
Expand Down Expand Up @@ -559,7 +559,7 @@
"\n",
"Set via `Flow.relative_minimum`:\n",
"```python\n",
"fx.Flow('Steam', bus='Steam', size=500, relative_minimum=0.3) # Min 30% load\n",
"fx.Flow(bus='Steam', size=500, relative_minimum=0.3) # Min 30% load\n",
"```\n",
"\n",
"### When Status is Active\n",
Expand Down
Loading