Piecewise Conversion¶
Model equipment with load-dependent efficiency using piecewise linear approximation.
User Story: A gas engine's efficiency varies with load - lower at part-load, optimal at mid-load. We want to capture this non-linear behavior in our optimization.
In [1]:
Copied!
import numpy as np
import pandas as pd
import flixopt as fx
fx.CONFIG.notebook()
import numpy as np import pandas as pd import flixopt as fx fx.CONFIG.notebook()
Out[1]:
flixopt.config.CONFIG
The Problem¶
Real equipment efficiency varies with operating point:
| Load Level | Electrical Efficiency | Reason |
|---|---|---|
| 25-50% (part load) | 32-38% | Throttling losses |
| 50-75% (mid load) | 38-42% | Near design point |
| 75-100% (full load) | 42-40% | Thermal limits |
A constant efficiency assumption misses this behavior.
Define the Efficiency Curve¶
Each Piece defines corresponding fuel input and electricity output ranges:
In [2]:
Copied!
piecewise_efficiency = fx.PiecewiseConversion(
{
'Fuel': fx.Piecewise(
[
fx.Piece(start=78, end=132), # Part load
fx.Piece(start=132, end=179), # Mid load
fx.Piece(start=179, end=250), # Full load
]
),
'Elec': fx.Piecewise(
[
fx.Piece(start=25, end=50), # 32% -> 38% efficiency
fx.Piece(start=50, end=75), # 38% -> 42% efficiency
fx.Piece(start=75, end=100), # 42% -> 40% efficiency
]
),
}
)
piecewise_efficiency = fx.PiecewiseConversion( { 'Fuel': fx.Piecewise( [ fx.Piece(start=78, end=132), # Part load fx.Piece(start=132, end=179), # Mid load fx.Piece(start=179, end=250), # Full load ] ), 'Elec': fx.Piecewise( [ fx.Piece(start=25, end=50), # 32% -> 38% efficiency fx.Piece(start=50, end=75), # 38% -> 42% efficiency fx.Piece(start=75, end=100), # 42% -> 40% efficiency ] ), } )
Build and Solve¶
In [3]:
Copied!
timesteps = pd.date_range('2024-01-22', periods=24, freq='h')
# Demand varies through the day (30-90 kW, within piecewise range 25-100)
elec_demand = 60 + 30 * np.sin(np.arange(24) * np.pi / 12)
fs = fx.FlowSystem(timesteps)
fs.add_elements(
fx.Bus('Gas'),
fx.Bus('Electricity'),
fx.Effect('costs', '€', is_standard=True, is_objective=True),
fx.Source('GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=300, effects_per_flow_hour=0.05)]),
fx.LinearConverter(
'GasEngine',
inputs=[fx.Flow('Fuel', bus='Gas')],
outputs=[fx.Flow('Elec', bus='Electricity')],
piecewise_conversion=piecewise_efficiency,
),
fx.Sink('Load', inputs=[fx.Flow('Elec', bus='Electricity', size=1, fixed_relative_profile=elec_demand)]),
)
fs.optimize(fx.solvers.HighsSolver());
timesteps = pd.date_range('2024-01-22', periods=24, freq='h') # Demand varies through the day (30-90 kW, within piecewise range 25-100) elec_demand = 60 + 30 * np.sin(np.arange(24) * np.pi / 12) fs = fx.FlowSystem(timesteps) fs.add_elements( fx.Bus('Gas'), fx.Bus('Electricity'), fx.Effect('costs', '€', is_standard=True, is_objective=True), fx.Source('GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=300, effects_per_flow_hour=0.05)]), fx.LinearConverter( 'GasEngine', inputs=[fx.Flow('Fuel', bus='Gas')], outputs=[fx.Flow('Elec', bus='Electricity')], piecewise_conversion=piecewise_efficiency, ), fx.Sink('Load', inputs=[fx.Flow('Elec', bus='Electricity', size=1, fixed_relative_profile=elec_demand)]), ) fs.optimize(fx.solvers.HighsSolver());
Visualize the Efficiency Curve¶
In [4]:
Copied!
fs.components['GasEngine'].piecewise_conversion.plot(x_flow='Fuel')
fs.components['GasEngine'].piecewise_conversion.plot(x_flow='Fuel')
Out[4]:
Results¶
In [5]:
Copied!
fs.statistics.plot.balance('Electricity')
fs.statistics.plot.balance('Electricity')
Out[5]:
In [6]:
Copied!
# Verify efficiency varies with load
fuel = fs.solution['GasEngine(Fuel)|flow_rate']
elec = fs.solution['GasEngine(Elec)|flow_rate']
efficiency = elec / fuel
print(f'Efficiency range: {float(efficiency.min()):.1%} - {float(efficiency.max()):.1%}')
print(f'Total cost: {fs.solution["costs"].item():.2f} €')
# Verify efficiency varies with load fuel = fs.solution['GasEngine(Fuel)|flow_rate'] elec = fs.solution['GasEngine(Elec)|flow_rate'] efficiency = elec / fuel print(f'Efficiency range: {float(efficiency.min()):.1%} - {float(efficiency.max()):.1%}') print(f'Total cost: {fs.solution["costs"].item():.2f} €')
Efficiency range: 33.8% - 41.9% Total cost: 182.96 €
Key Points¶
Syntax:
fx.PiecewiseConversion({
'Input': fx.Piecewise([fx.Piece(start=a, end=b), ...]),
'Output': fx.Piecewise([fx.Piece(start=x, end=y), ...]),
})
Rules:
- All flows must have the same number of segments
- Segments typically connect (end of N = start of N+1)
- Efficiency = output / input at each point
Time-varying: Pass arrays instead of scalars to model changing limits (e.g., temperature derating).
Next: See 06c-piecewise-effects for non-linear investment costs.