Quickstart¶
Heat a small workshop with a gas boiler - the minimal working example.
This notebook introduces the core concepts of flixopt:
- FlowSystem: The container for your energy system model
- Bus: Balance nodes where energy flows meet
- Effect: Quantities to track and optimize (costs, emissions)
- Components: Equipment like boilers, sources, and sinks
- Flow: Connections between components and buses
Setup¶
In [1]:
Copied!
import pandas as pd
import plotly.express as px
import xarray as xr
import flixopt as fx
fx.CONFIG.notebook()
import pandas as pd import plotly.express as px import xarray as xr import flixopt as fx fx.CONFIG.notebook()
Out[1]:
flixopt.config.CONFIG
Define the Time Horizon¶
Every optimization needs a time horizon. Here we model a simple 4-hour period:
In [2]:
Copied!
timesteps = pd.date_range('2024-01-15 08:00', periods=4, freq='h')
print(f'Optimizing from {timesteps[0]} to {timesteps[-1]}')
timesteps = pd.date_range('2024-01-15 08:00', periods=4, freq='h') print(f'Optimizing from {timesteps[0]} to {timesteps[-1]}')
Optimizing from 2024-01-15 08:00:00 to 2024-01-15 11:00:00
Define the Heat Demand¶
The workshop has varying heat demand throughout the morning:
In [3]:
Copied!
# Heat demand in kW for each hour - using xarray
heat_demand = xr.DataArray(
[30, 50, 45, 25],
dims=['time'],
coords={'time': timesteps},
name='Heat Demand [kW]',
)
# Visualize the demand with plotly
fig = px.bar(x=heat_demand.time.values, y=heat_demand.values, labels={'x': 'Time', 'y': 'Heat Demand [kW]'})
fig
# Heat demand in kW for each hour - using xarray heat_demand = xr.DataArray( [30, 50, 45, 25], dims=['time'], coords={'time': timesteps}, name='Heat Demand [kW]', ) # Visualize the demand with plotly fig = px.bar(x=heat_demand.time.values, y=heat_demand.values, labels={'x': 'Time', 'y': 'Heat Demand [kW]'}) fig
Build the Energy System Model¶
Now we create the FlowSystem and add all components:
Gas Supply ──► [Gas Bus] ──► Boiler ──► [Heat Bus] ──► Workshop
€ η=90% Demand
In [4]:
Copied!
# Create the FlowSystem container
flow_system = fx.FlowSystem(timesteps)
flow_system.add_elements(
# === Buses: Balance nodes for energy carriers ===
fx.Bus('Gas'), # Natural gas network connection
fx.Bus('Heat'), # Heat distribution within workshop
# === Effect: What we want to minimize ===
fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),
# === Gas Supply: Unlimited gas at 0.08 €/kWh ===
fx.Source(
'GasGrid',
outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.08)],
),
# === Boiler: Converts gas to heat at 90% efficiency ===
fx.linear_converters.Boiler(
'Boiler',
thermal_efficiency=0.9,
thermal_flow=fx.Flow('Heat', bus='Heat', size=100), # 100 kW capacity
fuel_flow=fx.Flow('Gas', bus='Gas'),
),
# === Workshop: Heat demand that must be met ===
fx.Sink(
'Workshop',
inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],
),
)
# Create the FlowSystem container flow_system = fx.FlowSystem(timesteps) flow_system.add_elements( # === Buses: Balance nodes for energy carriers === fx.Bus('Gas'), # Natural gas network connection fx.Bus('Heat'), # Heat distribution within workshop # === Effect: What we want to minimize === fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True), # === Gas Supply: Unlimited gas at 0.08 €/kWh === fx.Source( 'GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.08)], ), # === Boiler: Converts gas to heat at 90% efficiency === fx.linear_converters.Boiler( 'Boiler', thermal_efficiency=0.9, thermal_flow=fx.Flow('Heat', bus='Heat', size=100), # 100 kW capacity fuel_flow=fx.Flow('Gas', bus='Gas'), ), # === Workshop: Heat demand that must be met === fx.Sink( 'Workshop', inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand.values)], ), )
Run the Optimization¶
Now we solve the model using the HiGHS solver (open-source, included with flixopt):
In [5]:
Copied!
flow_system.optimize(fx.solvers.HighsSolver());
flow_system.optimize(fx.solvers.HighsSolver());
In [6]:
Copied!
flow_system.statistics.plot.balance('Heat')
flow_system.statistics.plot.balance('Heat')
Out[6]:
Total Costs¶
Access the optimized objective value:
In [7]:
Copied!
total_costs = flow_system.solution['costs'].item()
total_heat = float(heat_demand.sum())
gas_consumed = total_heat / 0.9 # Account for boiler efficiency
print(f'Total heat demand: {total_heat:.1f} kWh')
print(f'Gas consumed: {gas_consumed:.1f} kWh')
print(f'Total costs: {total_costs:.2f} €')
print(f'Average cost: {total_costs / total_heat:.3f} €/kWh_heat')
total_costs = flow_system.solution['costs'].item() total_heat = float(heat_demand.sum()) gas_consumed = total_heat / 0.9 # Account for boiler efficiency print(f'Total heat demand: {total_heat:.1f} kWh') print(f'Gas consumed: {gas_consumed:.1f} kWh') print(f'Total costs: {total_costs:.2f} €') print(f'Average cost: {total_costs / total_heat:.3f} €/kWh_heat')
Total heat demand: 150.0 kWh Gas consumed: 166.7 kWh Total costs: 13.33 € Average cost: 0.089 €/kWh_heat
Flow Rates Over Time¶
Visualize all flow rates using the built-in plotting accessor:
In [8]:
Copied!
# Plot all flow rates
flow_system.statistics.plot.flows()
# Plot all flow rates flow_system.statistics.plot.flows()
Out[8]:
Energy Flow Sankey¶
A Sankey diagram visualizes the total energy flows through the system:
In [9]:
Copied!
flow_system.statistics.plot.sankey.flows()
flow_system.statistics.plot.sankey.flows()
Out[9]:
Summary¶
In this quickstart, you learned the basic workflow:
- Create a
FlowSystemwith timesteps - Add buses, effects, and components
- Optimize with
flow_system.optimize(solver) - Analyze results via
flow_system.statistics
Next Steps¶
- 02-heat-system: Add thermal storage to shift loads
- 03-investment-optimization: Optimize equipment sizing