Transmission¶
Model energy or material transport between locations with losses.
This notebook covers:
- Transmission component: Connecting sites with pipelines, cables, or conveyors
- Transmission losses: Relative losses (proportional) and absolute losses (fixed)
- Bidirectional flow: Two-way transmission with flow direction constraints
- Capacity optimization: Sizing transmission infrastructure
Setup¶
In [1]:
Copied!
import numpy as np
import pandas as pd
import plotly.express as px
import xarray as xr
import flixopt as fx
fx.CONFIG.notebook()
import numpy as np 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
The Problem: Connecting Two Sites¶
Consider a district heating network with two sites:
- Site A: Has a large gas boiler (cheap production)
- Site B: Has a smaller electric boiler (expensive, but flexible)
A district heating pipe connects both sites. The question: How should heat flow between sites to minimize total costs?
Transmission Characteristics¶
| Parameter | Value | Description |
|---|---|---|
| Relative losses | 5% | Heat loss proportional to flow (pipe heat loss) |
| Capacity | 200 kW | Maximum transmission rate |
| Bidirectional | Yes | Heat can flow A→B or B→A |
Define Time Series Data¶
In [2]:
Copied!
# One week simulation
timesteps = pd.date_range('2024-01-22', periods=168, freq='h')
hours = np.arange(168)
hour_of_day = hours % 24
# Site A: Industrial facility with steady demand
demand_a_base = 150
demand_a_variation = 30 * np.sin(hour_of_day * np.pi / 12) # Day/night cycle
demand_a = demand_a_base + demand_a_variation
# Site B: Office building with peak during work hours
demand_b = np.where(
(hour_of_day >= 8) & (hour_of_day <= 18),
180, # Daytime: 180 kW
80, # Nighttime: 80 kW
)
# Add weekly pattern (lower on weekends)
day_of_week = (hours // 24) % 7
demand_b = np.where(day_of_week >= 5, demand_b * 0.6, demand_b) # Weekend reduction
# One week simulation timesteps = pd.date_range('2024-01-22', periods=168, freq='h') hours = np.arange(168) hour_of_day = hours % 24 # Site A: Industrial facility with steady demand demand_a_base = 150 demand_a_variation = 30 * np.sin(hour_of_day * np.pi / 12) # Day/night cycle demand_a = demand_a_base + demand_a_variation # Site B: Office building with peak during work hours demand_b = np.where( (hour_of_day >= 8) & (hour_of_day <= 18), 180, # Daytime: 180 kW 80, # Nighttime: 80 kW ) # Add weekly pattern (lower on weekends) day_of_week = (hours // 24) % 7 demand_b = np.where(day_of_week >= 5, demand_b * 0.6, demand_b) # Weekend reduction
In [3]:
Copied!
# Visualize demand profiles
fig = px.line(
x=timesteps.tolist() * 2,
y=np.concatenate([demand_a, demand_b]),
color=['Site A (Industrial)'] * 168 + ['Site B (Office)'] * 168,
title='Heat Demand at Both Sites',
labels={'x': 'Time', 'y': 'Heat Demand [kW]', 'color': 'Site'},
)
fig
# Visualize demand profiles fig = px.line( x=timesteps.tolist() * 2, y=np.concatenate([demand_a, demand_b]), color=['Site A (Industrial)'] * 168 + ['Site B (Office)'] * 168, title='Heat Demand at Both Sites', labels={'x': 'Time', 'y': 'Heat Demand [kW]', 'color': 'Site'}, ) fig
Example 1: Unidirectional Transmission¶
Start with a simple case: heat flows only from Site A to Site B.
In [4]:
Copied!
fs_unidirectional = fx.FlowSystem(timesteps)
fs_unidirectional.add_carriers(
fx.Carrier('gas', '#3498db', 'kW'),
fx.Carrier('electricity', '#f1c40f', 'kW'),
fx.Carrier('heat', '#e74c3c', 'kW'),
)
fs_unidirectional.add_elements(
# === Buses (one per site) ===
fx.Bus('Heat_A', carrier='heat'), # Site A heat network
fx.Bus('Heat_B', carrier='heat'), # Site B heat network
fx.Bus('Gas', carrier='gas'), # Gas supply network
fx.Bus('Electricity', carrier='electricity'), # Electricity grid
# === Effect ===
fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),
# === External supplies ===
fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]),
fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=0.25)]),
# === Site A: Large gas boiler (cheap) ===
fx.LinearConverter(
'GasBoiler_A',
inputs=[fx.Flow('Gas', bus='Gas', size=500)],
outputs=[fx.Flow('Heat', bus='Heat_A', size=400)],
conversion_factors=[{'Gas': 1, 'Heat': 0.92}], # 92% efficiency
),
# === Site B: Small electric boiler (expensive but flexible) ===
fx.LinearConverter(
'ElecBoiler_B',
inputs=[fx.Flow('Elec', bus='Electricity', size=250)],
outputs=[fx.Flow('Heat', bus='Heat_B', size=250)],
conversion_factors=[{'Elec': 1, 'Heat': 0.99}], # 99% efficiency
),
# === Transmission: A → B (unidirectional) ===
fx.Transmission(
'Pipe_A_to_B',
in1=fx.Flow('from_A', bus='Heat_A', size=200), # Input from Site A
out1=fx.Flow('to_B', bus='Heat_B', size=200), # Output to Site B
relative_losses=0.05, # 5% heat loss in pipe
),
# === Demands ===
fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]),
fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]),
)
fs_unidirectional.optimize(fx.solvers.HighsSolver());
fs_unidirectional = fx.FlowSystem(timesteps) fs_unidirectional.add_carriers( fx.Carrier('gas', '#3498db', 'kW'), fx.Carrier('electricity', '#f1c40f', 'kW'), fx.Carrier('heat', '#e74c3c', 'kW'), ) fs_unidirectional.add_elements( # === Buses (one per site) === fx.Bus('Heat_A', carrier='heat'), # Site A heat network fx.Bus('Heat_B', carrier='heat'), # Site B heat network fx.Bus('Gas', carrier='gas'), # Gas supply network fx.Bus('Electricity', carrier='electricity'), # Electricity grid # === Effect === fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True), # === External supplies === fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]), fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=0.25)]), # === Site A: Large gas boiler (cheap) === fx.LinearConverter( 'GasBoiler_A', inputs=[fx.Flow('Gas', bus='Gas', size=500)], outputs=[fx.Flow('Heat', bus='Heat_A', size=400)], conversion_factors=[{'Gas': 1, 'Heat': 0.92}], # 92% efficiency ), # === Site B: Small electric boiler (expensive but flexible) === fx.LinearConverter( 'ElecBoiler_B', inputs=[fx.Flow('Elec', bus='Electricity', size=250)], outputs=[fx.Flow('Heat', bus='Heat_B', size=250)], conversion_factors=[{'Elec': 1, 'Heat': 0.99}], # 99% efficiency ), # === Transmission: A → B (unidirectional) === fx.Transmission( 'Pipe_A_to_B', in1=fx.Flow('from_A', bus='Heat_A', size=200), # Input from Site A out1=fx.Flow('to_B', bus='Heat_B', size=200), # Output to Site B relative_losses=0.05, # 5% heat loss in pipe ), # === Demands === fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]), fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]), ) fs_unidirectional.optimize(fx.solvers.HighsSolver());
In [5]:
Copied!
# View results
print(f'Total cost: {fs_unidirectional.solution["costs"].item():.2f} €')
# View results print(f'Total cost: {fs_unidirectional.solution["costs"].item():.2f} €')
Total cost: 2479.00 €
In [6]:
Copied!
# Heat balance at Site A
fs_unidirectional.statistics.plot.balance('Heat_A')
# Heat balance at Site A fs_unidirectional.statistics.plot.balance('Heat_A')
Out[6]:
In [7]:
Copied!
# Heat balance at Site B
fs_unidirectional.statistics.plot.balance('Heat_B')
# Heat balance at Site B fs_unidirectional.statistics.plot.balance('Heat_B')
Out[7]:
In [8]:
Copied!
# Energy flow overview
fs_unidirectional.statistics.plot.sankey.flows()
# Energy flow overview fs_unidirectional.statistics.plot.sankey.flows()
Out[8]:
Observations¶
- The optimizer uses the cheaper gas boiler at Site A as much as possible
- Heat is transmitted to Site B (despite 5% losses) because gas is much cheaper than electricity
- The electric boiler at Site B only runs when transmission capacity is insufficient
Example 2: Bidirectional Transmission¶
Now allow heat to flow in both directions. This is useful when:
- Both sites have generation capacity
- Demand patterns differ between sites
- Prices or availability vary over time
In [9]:
Copied!
# Add a heat pump at Site B (cheaper during certain hours)
# Electricity price varies: cheap at night, expensive during day
elec_price = np.where(
(hour_of_day >= 22) | (hour_of_day <= 6),
0.08, # Night: 0.08 €/kWh
0.25, # Day: 0.25 €/kWh
)
# Add a heat pump at Site B (cheaper during certain hours) # Electricity price varies: cheap at night, expensive during day elec_price = np.where( (hour_of_day >= 22) | (hour_of_day <= 6), 0.08, # Night: 0.08 €/kWh 0.25, # Day: 0.25 €/kWh )
In [10]:
Copied!
fs_bidirectional = fx.FlowSystem(timesteps)
fs_bidirectional.add_carriers(
fx.Carrier('gas', '#3498db', 'kW'),
fx.Carrier('electricity', '#f1c40f', 'kW'),
fx.Carrier('heat', '#e74c3c', 'kW'),
)
fs_bidirectional.add_elements(
# === Buses ===
fx.Bus('Heat_A', carrier='heat'),
fx.Bus('Heat_B', carrier='heat'),
fx.Bus('Gas', carrier='gas'),
fx.Bus('Electricity', carrier='electricity'),
# === Effect ===
fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),
# === External supplies ===
fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]),
fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=elec_price)]),
# === Site A: Gas boiler ===
fx.LinearConverter(
'GasBoiler_A',
inputs=[fx.Flow('Gas', bus='Gas', size=500)],
outputs=[fx.Flow('Heat', bus='Heat_A', size=400)],
conversion_factors=[{'Gas': 1, 'Heat': 0.92}],
),
# === Site B: Heat pump (efficient with variable electricity price) ===
fx.LinearConverter(
'HeatPump_B',
inputs=[fx.Flow('Elec', bus='Electricity', size=100)],
outputs=[fx.Flow('Heat', bus='Heat_B', size=350)],
conversion_factors=[{'Elec': 1, 'Heat': 3.5}], # COP = 3.5
),
# === BIDIRECTIONAL Transmission ===
fx.Transmission(
'Pipe_AB',
# Direction 1: A → B
in1=fx.Flow('from_A', bus='Heat_A', size=200),
out1=fx.Flow('to_B', bus='Heat_B', size=200),
# Direction 2: B → A
in2=fx.Flow('from_B', bus='Heat_B', size=200),
out2=fx.Flow('to_A', bus='Heat_A', size=200),
relative_losses=0.05,
prevent_simultaneous_flows_in_both_directions=True, # Can't flow both ways at once
),
# === Demands ===
fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]),
fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]),
)
fs_bidirectional.optimize(fx.solvers.HighsSolver());
fs_bidirectional = fx.FlowSystem(timesteps) fs_bidirectional.add_carriers( fx.Carrier('gas', '#3498db', 'kW'), fx.Carrier('electricity', '#f1c40f', 'kW'), fx.Carrier('heat', '#e74c3c', 'kW'), ) fs_bidirectional.add_elements( # === Buses === fx.Bus('Heat_A', carrier='heat'), fx.Bus('Heat_B', carrier='heat'), fx.Bus('Gas', carrier='gas'), fx.Bus('Electricity', carrier='electricity'), # === Effect === fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True), # === External supplies === fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]), fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=elec_price)]), # === Site A: Gas boiler === fx.LinearConverter( 'GasBoiler_A', inputs=[fx.Flow('Gas', bus='Gas', size=500)], outputs=[fx.Flow('Heat', bus='Heat_A', size=400)], conversion_factors=[{'Gas': 1, 'Heat': 0.92}], ), # === Site B: Heat pump (efficient with variable electricity price) === fx.LinearConverter( 'HeatPump_B', inputs=[fx.Flow('Elec', bus='Electricity', size=100)], outputs=[fx.Flow('Heat', bus='Heat_B', size=350)], conversion_factors=[{'Elec': 1, 'Heat': 3.5}], # COP = 3.5 ), # === BIDIRECTIONAL Transmission === fx.Transmission( 'Pipe_AB', # Direction 1: A → B in1=fx.Flow('from_A', bus='Heat_A', size=200), out1=fx.Flow('to_B', bus='Heat_B', size=200), # Direction 2: B → A in2=fx.Flow('from_B', bus='Heat_B', size=200), out2=fx.Flow('to_A', bus='Heat_A', size=200), relative_losses=0.05, prevent_simultaneous_flows_in_both_directions=True, # Can't flow both ways at once ), # === Demands === fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]), fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]), ) fs_bidirectional.optimize(fx.solvers.HighsSolver());
In [11]:
Copied!
# Compare costs
print(f'Unidirectional cost: {fs_unidirectional.solution["costs"].item():.2f} €')
print(f'Bidirectional cost: {fs_bidirectional.solution["costs"].item():.2f} €')
savings = fs_unidirectional.solution['costs'].item() - fs_bidirectional.solution['costs'].item()
print(f'Savings from bidirectional: {savings:.2f} €')
# Compare costs print(f'Unidirectional cost: {fs_unidirectional.solution["costs"].item():.2f} €') print(f'Bidirectional cost: {fs_bidirectional.solution["costs"].item():.2f} €') savings = fs_unidirectional.solution['costs'].item() - fs_bidirectional.solution['costs'].item() print(f'Savings from bidirectional: {savings:.2f} €')
Unidirectional cost: 2479.00 € Bidirectional cost: 2479.00 € Savings from bidirectional: 0.00 €
In [12]:
Copied!
# Visualize transmission flows in both directions using xarray
flow_data = xr.Dataset(
{
'A_to_B': fs_bidirectional.solution['Pipe_AB(from_A)|flow_rate'],
'B_to_A': fs_bidirectional.solution['Pipe_AB(from_B)|flow_rate'],
}
)
fig = px.line(
x=list(flow_data['time'].values) * 2,
y=np.concatenate([flow_data['A_to_B'].values, flow_data['B_to_A'].values]),
color=['A → B'] * len(flow_data['time']) + ['B → A'] * len(flow_data['time']),
title='Transmission Flow Direction Over Time',
labels={'x': 'Time', 'y': 'Flow Rate [kW]', 'color': 'Direction'},
)
fig
# Visualize transmission flows in both directions using xarray flow_data = xr.Dataset( { 'A_to_B': fs_bidirectional.solution['Pipe_AB(from_A)|flow_rate'], 'B_to_A': fs_bidirectional.solution['Pipe_AB(from_B)|flow_rate'], } ) fig = px.line( x=list(flow_data['time'].values) * 2, y=np.concatenate([flow_data['A_to_B'].values, flow_data['B_to_A'].values]), color=['A → B'] * len(flow_data['time']) + ['B → A'] * len(flow_data['time']), title='Transmission Flow Direction Over Time', labels={'x': 'Time', 'y': 'Flow Rate [kW]', 'color': 'Direction'}, ) fig
In [13]:
Copied!
# Heat balance at Site B showing bidirectional flows
fs_bidirectional.statistics.plot.balance('Heat_B')
# Heat balance at Site B showing bidirectional flows fs_bidirectional.statistics.plot.balance('Heat_B')
Out[13]:
In [14]:
Copied!
# Energy flow overview
fs_bidirectional.statistics.plot.sankey.flows()
# Energy flow overview fs_bidirectional.statistics.plot.sankey.flows()
Out[14]:
Observations¶
- During cheap electricity hours (night): Heat pump at Site B produces heat, some flows to Site A
- During expensive electricity hours (day): Gas boiler at Site A supplies both sites
- The bidirectional transmission enables load shifting and arbitrage between sites
Example 3: Transmission Capacity Optimization¶
What's the optimal pipe capacity? Let the optimizer decide.
In [15]:
Copied!
# Daily amortized pipe cost (simplified)
PIPE_COST_PER_KW = 0.05 # €/kW/day capacity cost
fs_invest = fx.FlowSystem(timesteps)
fs_invest.add_carriers(
fx.Carrier('gas', '#3498db', 'kW'),
fx.Carrier('electricity', '#f1c40f', 'kW'),
fx.Carrier('heat', '#e74c3c', 'kW'),
)
fs_invest.add_elements(
# === Buses ===
fx.Bus('Heat_A', carrier='heat'),
fx.Bus('Heat_B', carrier='heat'),
fx.Bus('Gas', carrier='gas'),
fx.Bus('Electricity', carrier='electricity'),
# === Effect ===
fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),
# === External supplies ===
fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]),
fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=elec_price)]),
# === Site A: Gas boiler ===
fx.LinearConverter(
'GasBoiler_A',
inputs=[fx.Flow('Gas', bus='Gas', size=500)],
outputs=[fx.Flow('Heat', bus='Heat_A', size=400)],
conversion_factors=[{'Gas': 1, 'Heat': 0.92}],
),
# === Site B: Heat pump ===
fx.LinearConverter(
'HeatPump_B',
inputs=[fx.Flow('Elec', bus='Electricity', size=100)],
outputs=[fx.Flow('Heat', bus='Heat_B', size=350)],
conversion_factors=[{'Elec': 1, 'Heat': 3.5}],
),
# === Site B: Backup electric boiler ===
fx.LinearConverter(
'ElecBoiler_B',
inputs=[fx.Flow('Elec', bus='Electricity', size=200)],
outputs=[fx.Flow('Heat', bus='Heat_B', size=200)],
conversion_factors=[{'Elec': 1, 'Heat': 0.99}],
),
# === Transmission with INVESTMENT OPTIMIZATION ===
# Investment parameters are passed via 'size' parameter
fx.Transmission(
'Pipe_AB',
in1=fx.Flow(
'from_A',
bus='Heat_A',
size=fx.InvestParameters(
effects_of_investment_per_size={'costs': PIPE_COST_PER_KW * 7}, # Weekly cost
minimum_size=0,
maximum_size=300,
),
),
out1=fx.Flow('to_B', bus='Heat_B'),
in2=fx.Flow(
'from_B',
bus='Heat_B',
size=fx.InvestParameters(
effects_of_investment_per_size={'costs': PIPE_COST_PER_KW * 7},
minimum_size=0,
maximum_size=300,
),
),
out2=fx.Flow('to_A', bus='Heat_A'),
relative_losses=0.05,
balanced=True, # Same capacity in both directions
prevent_simultaneous_flows_in_both_directions=True,
),
# === Demands ===
fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]),
fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]),
)
fs_invest.optimize(fx.solvers.HighsSolver());
# Daily amortized pipe cost (simplified) PIPE_COST_PER_KW = 0.05 # €/kW/day capacity cost fs_invest = fx.FlowSystem(timesteps) fs_invest.add_carriers( fx.Carrier('gas', '#3498db', 'kW'), fx.Carrier('electricity', '#f1c40f', 'kW'), fx.Carrier('heat', '#e74c3c', 'kW'), ) fs_invest.add_elements( # === Buses === fx.Bus('Heat_A', carrier='heat'), fx.Bus('Heat_B', carrier='heat'), fx.Bus('Gas', carrier='gas'), fx.Bus('Electricity', carrier='electricity'), # === Effect === fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True), # === External supplies === fx.Source('GasSupply', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]), fx.Source('ElecGrid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=elec_price)]), # === Site A: Gas boiler === fx.LinearConverter( 'GasBoiler_A', inputs=[fx.Flow('Gas', bus='Gas', size=500)], outputs=[fx.Flow('Heat', bus='Heat_A', size=400)], conversion_factors=[{'Gas': 1, 'Heat': 0.92}], ), # === Site B: Heat pump === fx.LinearConverter( 'HeatPump_B', inputs=[fx.Flow('Elec', bus='Electricity', size=100)], outputs=[fx.Flow('Heat', bus='Heat_B', size=350)], conversion_factors=[{'Elec': 1, 'Heat': 3.5}], ), # === Site B: Backup electric boiler === fx.LinearConverter( 'ElecBoiler_B', inputs=[fx.Flow('Elec', bus='Electricity', size=200)], outputs=[fx.Flow('Heat', bus='Heat_B', size=200)], conversion_factors=[{'Elec': 1, 'Heat': 0.99}], ), # === Transmission with INVESTMENT OPTIMIZATION === # Investment parameters are passed via 'size' parameter fx.Transmission( 'Pipe_AB', in1=fx.Flow( 'from_A', bus='Heat_A', size=fx.InvestParameters( effects_of_investment_per_size={'costs': PIPE_COST_PER_KW * 7}, # Weekly cost minimum_size=0, maximum_size=300, ), ), out1=fx.Flow('to_B', bus='Heat_B'), in2=fx.Flow( 'from_B', bus='Heat_B', size=fx.InvestParameters( effects_of_investment_per_size={'costs': PIPE_COST_PER_KW * 7}, minimum_size=0, maximum_size=300, ), ), out2=fx.Flow('to_A', bus='Heat_A'), relative_losses=0.05, balanced=True, # Same capacity in both directions prevent_simultaneous_flows_in_both_directions=True, ), # === Demands === fx.Sink('Demand_A', inputs=[fx.Flow('Heat', bus='Heat_A', size=1, fixed_relative_profile=demand_a)]), fx.Sink('Demand_B', inputs=[fx.Flow('Heat', bus='Heat_B', size=1, fixed_relative_profile=demand_b)]), ) fs_invest.optimize(fx.solvers.HighsSolver());
In [16]:
Copied!
# Results
optimal_capacity = fs_invest.solution['Pipe_AB(from_A)|size'].item()
total_cost = fs_invest.solution['costs'].item()
print(f'Optimal pipe capacity: {optimal_capacity:.1f} kW')
print(f'Total cost: {total_cost:.2f} €')
# Results optimal_capacity = fs_invest.solution['Pipe_AB(from_A)|size'].item() total_cost = fs_invest.solution['costs'].item() print(f'Optimal pipe capacity: {optimal_capacity:.1f} kW') print(f'Total cost: {total_cost:.2f} €')
Optimal pipe capacity: 189.5 kW Total cost: 2611.63 €
In [17]:
Copied!
# Effect breakdown by component
fs_invest.statistics.plot.effects()
# Effect breakdown by component fs_invest.statistics.plot.effects()
Out[17]:
In [18]:
Copied!
# Energy flows
fs_invest.statistics.plot.sankey.flows()
# Energy flows fs_invest.statistics.plot.sankey.flows()
Out[18]:
Key Concepts¶
Transmission Component Structure¶
fx.Transmission(
label='pipe_name',
# Direction 1: A → B
in1=fx.Flow('from_A', bus='Bus_A', size=100),
out1=fx.Flow('to_B', bus='Bus_B', size=100),
# Direction 2: B → A (optional - omit for unidirectional)
in2=fx.Flow('from_B', bus='Bus_B', size=100),
out2=fx.Flow('to_A', bus='Bus_A', size=100),
# Loss parameters
relative_losses=0.05, # 5% proportional loss
absolute_losses=10, # 10 kW fixed loss when active (optional)
# Operational constraints
prevent_simultaneous_flows_in_both_directions=True,
balanced=True, # Same capacity both directions (needs InvestParameters)
)
Loss Types¶
| Loss Type | Formula | Use Case |
|---|---|---|
| Relative | out = in × (1 - loss) | Heat pipes, electrical lines |
| Absolute | out = in - loss (when active) | Pump energy, standby losses |
Bidirectional vs Unidirectional¶
| Configuration | Parameters | Use Case |
|---|---|---|
| Unidirectional | in1, out1 only | One-way pipelines, conveyors |
| Bidirectional | in1, out1, in2, out2 | Power lines, reversible pipes |
Investment Optimization¶
Use InvestParameters as the size parameter for capacity optimization:
in1=fx.Flow(
'from_A',
bus='Bus_A',
size=fx.InvestParameters( # Pass InvestParameters as size
effects_of_investment_per_size={'costs': cost_per_kw},
minimum_size=0,
maximum_size=500,
),
)
Common Use Cases¶
| Application | Typical Losses | Notes |
|---|---|---|
| District heating pipe | 2-10% relative | Temperature-dependent |
| High voltage line | 1-5% relative | Distance-dependent |
| Natural gas pipeline | 0.5-2% relative | Compressor energy as absolute loss |
| Conveyor belt | Fixed absolute | Motor energy consumption |
| Hydrogen pipeline | 1-3% relative | Compression losses |
Summary¶
You learned how to:
- Create unidirectional transmission between two buses
- Model bidirectional transmission with flow direction constraints
- Apply relative and absolute losses to transmission
- Optimize transmission capacity using InvestParameters
- Analyze multi-site energy systems with interconnections
Next Steps¶
- 07-scenarios-and-periods: Multi-year planning with uncertainty
- 08a-Aggregation: Speed up large problems with time series aggregation
- 08b-Rolling Horizon: Decompose large problems into sequential segments