Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.pyc
*.log
*.nc4
*.nc
results/
.idea/
.venv/
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ repos:
rev: 0.8.2
hooks:
- id: nbstripout
files: ^docs/examples.*\.ipynb$
files: ^docs/.*\.ipynb$
14 changes: 13 additions & 1 deletion docs/notebooks/01-quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": "# Quickstart\n\nHeat a small workshop with a gas boiler - the minimal working example.\n\nThis notebook introduces the **core concepts** of flixopt:\n\n- **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- **Flow**: Connections between components and buses"
"source": [
"# Quickstart\n",
"\n",
"Heat a small workshop with a gas boiler - the minimal working example.\n",
"\n",
"This notebook introduces the **core concepts** of flixopt:\n",
"\n",
"- **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",
"- **Flow**: Connections between components and buses"
]
},
{
"cell_type": "markdown",
Expand Down
57 changes: 55 additions & 2 deletions docs/notebooks/02-heat-system.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": "# Heat System\n\nDistrict heating with thermal storage and time-varying prices.\n\nThis notebook introduces:\n\n- **Storage**: Thermal buffer tanks with charging/discharging\n- **Time series data**: Using real demand profiles\n- **Multiple components**: Combining boiler, storage, and loads\n- **Result visualization**: Heatmaps, balance plots, and charge states"
"source": [
"# Heat System\n",
"\n",
"District heating with thermal storage and time-varying prices.\n",
"\n",
"This notebook introduces:\n",
"\n",
"- **Storage**: Thermal buffer tanks with charging/discharging\n",
"- **Time series data**: Using real demand profiles\n",
"- **Multiple components**: Combining boiler, storage, and loads\n",
"- **Result visualization**: Heatmaps, balance plots, and charge states"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -149,7 +160,49 @@
"id": "9",
"metadata": {},
"outputs": [],
"source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # === Buses ===\n fx.Bus('Gas', carrier='gas'),\n fx.Bus('Heat', carrier='heat'),\n # === Effect ===\n fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n # === 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 ),\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 ),\n # === Thermal Storage: 500 kWh tank ===\n fx.Storage(\n 'ThermalStorage',\n capacity_in_flow_hours=500, # 500 kWh capacity\n initial_charge_state=250, # Start half-full\n minimal_final_charge_state=200, # End with at least 200 kWh\n 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 ),\n # === Office Heat Demand ===\n fx.Sink(\n 'Office',\n inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n ),\n)"
"source": [
"flow_system = fx.FlowSystem(timesteps)\n",
"flow_system.add_carriers(\n",
" fx.Carrier('gas', '#3498db', 'kW'),\n",
" fx.Carrier('heat', '#e74c3c', 'kW'),\n",
")\n",
"flow_system.add_elements(\n",
" # === Buses ===\n",
" fx.Bus('Gas', carrier='gas'),\n",
" fx.Bus('Heat', carrier='heat'),\n",
" # === Effect ===\n",
" fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
" # === 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",
" ),\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",
" ),\n",
" # === Thermal Storage: 500 kWh tank ===\n",
" fx.Storage(\n",
" 'ThermalStorage',\n",
" capacity_in_flow_hours=500, # 500 kWh capacity\n",
" initial_charge_state=250, # Start half-full\n",
" minimal_final_charge_state=200, # End with at least 200 kWh\n",
" 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",
" ),\n",
" # === Office Heat Demand ===\n",
" fx.Sink(\n",
" 'Office',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
Expand Down
107 changes: 91 additions & 16 deletions docs/notebooks/03-investment-optimization.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": "# Sizing\n\nSize a solar heating system - let the optimizer decide equipment sizes.\n\nThis notebook introduces:\n\n- **InvestParameters**: Define investment decisions with size bounds and costs\n- **Investment costs**: Fixed costs and size-dependent costs\n- **Optimal sizing**: Let the optimizer find the best equipment sizes\n- **Trade-off analysis**: Balance investment vs. operating costs"
"source": [
"# Sizing\n",
"\n",
"Size a solar heating system - let the optimizer decide equipment sizes.\n",
"\n",
"This notebook introduces:\n",
"\n",
"- **InvestParameters**: Define investment decisions with size bounds and costs\n",
"- **Investment costs**: Fixed costs and size-dependent costs\n",
"- **Optimal sizing**: Let the optimizer find the best equipment sizes\n",
"- **Trade-off analysis**: Balance investment vs. operating costs"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -171,7 +182,71 @@
"id": "10",
"metadata": {},
"outputs": [],
"source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # === Buses ===\n fx.Bus('Heat', carrier='heat'),\n fx.Bus('Gas', carrier='gas'),\n # === Effects ===\n fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n # === Gas Supply ===\n fx.Source(\n 'GasGrid',\n outputs=[fx.Flow('Gas', 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 ),\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 minimum_size=0,\n maximum_size=500,\n effects_of_investment_per_size={'costs': SOLAR_COST_WEEKLY},\n ),\n # Solar output depends on radiation profile\n fixed_relative_profile=solar_profile,\n )\n ],\n ),\n # === Buffer Tank (size to be optimized) ===\n fx.Storage(\n 'BufferTank',\n # Investment optimization: find optimal capacity between 0-2000 kWh\n capacity_in_flow_hours=fx.InvestParameters(\n minimum_size=0,\n maximum_size=2000,\n effects_of_investment_per_size={'costs': TANK_COST_WEEKLY},\n ),\n initial_charge_state=0,\n 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 ),\n # === Pool Heat Demand ===\n fx.Sink(\n 'Pool',\n inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n ),\n)"
"source": [
"flow_system = fx.FlowSystem(timesteps)\n",
"flow_system.add_carriers(\n",
" fx.Carrier('gas', '#3498db', 'kW'),\n",
" fx.Carrier('heat', '#e74c3c', 'kW'),\n",
")\n",
"flow_system.add_elements(\n",
" # === Buses ===\n",
" fx.Bus('Heat', carrier='heat'),\n",
" fx.Bus('Gas', carrier='gas'),\n",
" # === Effects ===\n",
" fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
" # === Gas Supply ===\n",
" fx.Source(\n",
" 'GasGrid',\n",
" outputs=[fx.Flow('Gas', 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",
" ),\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",
" minimum_size=0,\n",
" maximum_size=500,\n",
" effects_of_investment_per_size={'costs': SOLAR_COST_WEEKLY},\n",
" ),\n",
" # Solar output depends on radiation profile\n",
" fixed_relative_profile=solar_profile,\n",
" )\n",
" ],\n",
" ),\n",
" # === Buffer Tank (size to be optimized) ===\n",
" fx.Storage(\n",
" 'BufferTank',\n",
" # Investment optimization: find optimal capacity between 0-2000 kWh\n",
" capacity_in_flow_hours=fx.InvestParameters(\n",
" minimum_size=0,\n",
" maximum_size=2000,\n",
" effects_of_investment_per_size={'costs': TANK_COST_WEEKLY},\n",
" ),\n",
" initial_charge_state=0,\n",
" 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",
" ),\n",
" # === Pool Heat Demand ===\n",
" fx.Sink(\n",
" 'Pool',\n",
" inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -207,7 +282,14 @@
"id": "14",
"metadata": {},
"outputs": [],
"source": "solar_size = flow_system.statistics.sizes['SolarCollectors(Heat)'].item()\ntank_size = flow_system.statistics.sizes['BufferTank'].item()\n\nprint('=== Optimal Investment Decisions ===')\nprint(f'Solar collectors: {solar_size:.1f} kW')\nprint(f'Buffer tank: {tank_size:.1f} kWh')\nprint(f'Tank-to-solar ratio: {tank_size / solar_size:.1f} kWh/kW' if solar_size > 0 else 'N/A')"
"source": [
"solar_size = flow_system.statistics.sizes['SolarCollectors(Heat)'].item()\n",
"tank_size = flow_system.statistics.sizes['BufferTank'].item()\n",
"\n",
"print(\n",
" f'Optimal sizes: Solar {solar_size:.0f} kW, Tank {tank_size:.0f} kWh (ratio: {tank_size / solar_size:.1f} kWh/kW)'\n",
")"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -249,12 +331,9 @@
"tank_invest = tank_size * TANK_COST_WEEKLY\n",
"gas_costs = total_costs - solar_invest - tank_invest\n",
"\n",
"print('=== Weekly Cost Breakdown ===')\n",
"print(f'Solar investment: {solar_invest:.2f} € ({solar_invest / total_costs * 100:.1f}%)')\n",
"print(f'Tank investment: {tank_invest:.2f} € ({tank_invest / total_costs * 100:.1f}%)')\n",
"print(f'Gas operating: {gas_costs:.2f} € ({gas_costs / total_costs * 100:.1f}%)')\n",
"print('─────────────────────────────')\n",
"print(f'Total: {total_costs:.2f} €')"
"print(\n",
" f'Weekly costs: Solar {solar_invest:.1f}€ ({solar_invest / total_costs * 100:.0f}%) + Tank {tank_invest:.1f}€ ({tank_invest / total_costs * 100:.0f}%) + Gas {gas_costs:.1f}€ ({gas_costs / total_costs * 100:.0f}%) = {total_costs:.1f}€'\n",
")"
]
},
{
Expand Down Expand Up @@ -317,13 +396,9 @@
"gas_only_cost = total_demand / 0.92 * GAS_PRICE # All heat from gas boiler\n",
"\n",
"savings = gas_only_cost - total_costs\n",
"savings_pct = savings / gas_only_cost * 100\n",
"\n",
"print('=== Comparison with Gas-Only ===')\n",
"print(f'Gas-only cost: {gas_only_cost:.2f} €/week')\n",
"print(f'With solar: {total_costs:.2f} €/week')\n",
"print(f'Savings: {savings:.2f} €/week ({savings_pct:.1f}%)')\n",
"print(f'Annual savings: {savings * 52:.0f} €/year')"
"print(\n",
" f'Solar saves {savings:.1f}€/week ({savings / gas_only_cost * 100:.0f}%) vs gas-only ({gas_only_cost:.1f}€) → {savings * 52:.0f}€/year'\n",
")"
]
},
{
Expand Down
Loading