Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 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
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
8 changes: 3 additions & 5 deletions docs/home/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ solar_profile = np.array([0, 0, 0, 0, 0, 0, 0.2, 0.5, 0.8, 1.0,
solar = fx.Source(
'solar',
outputs=[fx.Flow(
'power',
bus='electricity',
size=100, # 100 kW capacity
relative_maximum=solar_profile
Expand All @@ -67,17 +66,16 @@ demand_profile = np.array([30, 25, 20, 20, 25, 35, 50, 70, 80, 75,
60, 50, 40, 35])

demand = fx.Sink('demand', inputs=[
fx.Flow('consumption',
bus='electricity',
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
8 changes: 4 additions & 4 deletions docs/notebooks/01-quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,19 @@
" # === Gas Supply: Unlimited gas at 0.08 €/kWh ===\n",
" fx.Source(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.08)],\n",
" outputs=[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",
" '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",
" 'Workshop',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],\n",
" inputs=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],\n",
" ),\n",
")"
]
Expand Down
12 changes: 6 additions & 6 deletions docs/notebooks/02-heat-system.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@
" # === Gas Supply with time-varying price ===\n",
" fx.Source(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=gas_price)],\n",
" outputs=[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",
" '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",
" 'Office',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n",
" inputs=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n",
" ),\n",
")"
]
Expand Down
13 changes: 6 additions & 7 deletions docs/notebooks/03-investment-optimization.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -141,21 +141,20 @@
" # === Gas Supply ===\n",
" fx.Source(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=GAS_PRICE)],\n",
" outputs=[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",
" '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",
" 'SolarCollectors',\n",
" outputs=[\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",
" 'Pool',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n",
" inputs=[fx.Flow(bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n",
" ),\n",
")"
]
Expand Down
24 changes: 12 additions & 12 deletions docs/notebooks/04-operational-constraints.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
" # === Gas Supply ===\n",
" fx.Source(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)],\n",
" outputs=[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",
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",
" '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",
" 'Factory',\n",
" inputs=[fx.Flow('Steam', bus='Steam', size=1, fixed_relative_profile=steam_demand)],\n",
" inputs=[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.Source('GasGrid', outputs=[fx.Flow(bus='Gas', size=1000, effects_per_flow_hour=0.06)]),\n",
" # Main boiler WITHOUT status parameters\n",
" fx.linear_converters.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",
" '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.Sink('Factory', inputs=[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