Sizing (Investment Optimization)¶
Sizing lets the solver decide the optimal capacity of a flow or storage,
instead of fixing it upfront. This models investment decisions: how large
should a boiler, PV array, or battery be?
See Sizing (Math) for the formulation.
Basic Usage¶
Pass a Sizing object instead of a numeric size:
from fluxopt import Flow, Sizing
# Solver picks optimal size between 50 and 200 MW
source = Flow('elec', size=Sizing(min_size=50, max_size=200, mandatory=True))
Mandatory vs Optional¶
Mandatory (mandatory=True, default)¶
The component must be built. The solver picks a continuous size in
[min_size, max_size]. No binary variable needed — faster to solve:
With min_size=0, the solver can choose size=0, effectively not building
the component — but without the overhead of a binary indicator variable:
# Size in [0, 200] MW — solver may pick 0 (no binary needed)
Flow('elec', size=Sizing(min_size=0, max_size=200))
Optional (mandatory=False)¶
The solver decides whether to build the component. A binary indicator
variable gates the size: either 0 (not built) or in [min_size, max_size].
Use this when you need effects_fixed (one-time costs gated by the indicator)
or when min_size > 0 must be enforced only if built:
# Built at [50, 200] MW or not built at all
Flow('elec', size=Sizing(min_size=50, max_size=200, mandatory=False))
Binary Invest (fixed-size yes/no)¶
When min_size == max_size with mandatory=False, it becomes a binary invest
decision — build at exactly that size or not at all:
# Either build a 100 MW unit or nothing
Flow('elec', size=Sizing(min_size=100, max_size=100, mandatory=False))
Investment Effects¶
Sizing can contribute to tracked effects (cost, CO2, etc.) via two mechanisms:
Per-Size Effects¶
Cost proportional to the invested size (e.g., EUR/MW or kg_CO2/MW):
# 500 €/MW investment cost
Flow(
'elec',
size=Sizing(min_size=50, max_size=200, mandatory=True, effects_per_size={'cost': 500}),
)
Fixed Effects¶
One-time cost charged when the component is built. Only applies to optional
sizing (mandatory=False) since it's gated by the binary indicator:
# 10,000 € fixed cost if built, plus 500 €/MW
Flow(
'elec',
size=Sizing(
min_size=50, max_size=200, mandatory=False,
effects_per_size={'cost': 500},
effects_fixed={'cost': 10_000},
),
)
Storage Sizing¶
Storage.capacity also accepts Sizing for optimizing battery or tank size:
from fluxopt import Flow, Sizing, Storage
charge = Flow('elec', size=50)
discharge = Flow('elec', size=50)
battery = Storage(
'battery',
charging=charge,
discharging=discharge,
capacity=Sizing(min_size=100, max_size=1000, mandatory=True, effects_per_size={'cost': 200}),
)
Interaction with Other Features¶
With Bounds¶
Relative bounds (relative_minimum, relative_maximum) are fractions of the
optimized size variable, not a fixed number. If the solver picks 80 MW and
relative_minimum=0.3, the minimum flow rate is 24 MW.
With Status¶
When a flow has both Sizing and Status, a big-M formulation decouples the
binary on/off from the continuous size. See Status for details.
Full Example¶
Two competing sources — the solver decides whether to invest in a cheaper source:
from datetime import datetime
from fluxopt import Carrier, Effect, Flow, Port, Sizing, optimize
timesteps = [datetime(2024, 1, 1, h) for h in range(4)]
demand = Flow('elec', size=100, fixed_relative_profile=[0.5, 0.5, 0.5, 0.5])
# Always-available expensive source
grid = Flow('elec', size=200, effects_per_flow_hour={'cost': 0.10})
# Optional cheap source with investment cost
solar = Flow(
'elec',
size=Sizing(min_size=50, max_size=200, mandatory=False, effects_per_size={'cost': 20}),
effects_per_flow_hour={'cost': 0.01},
fixed_relative_profile=[0.0, 0.8, 0.8, 0.0], # only available midday
)
elec = Carrier('elec')
result = optimize(
timesteps=timesteps,
carriers=[elec],
effects=[Effect('cost', is_objective=True)],
ports=[
Port('grid', imports=[grid]),
Port('solar', imports=[solar]),
Port('demand', exports=[demand]),
],
)
print(f"Objective: {result.objective:.2f}")
Parameters Summary¶
| Parameter | Type | Default | Description |
|---|---|---|---|
min_size |
float |
required | Minimum size if invested |
max_size |
float |
required | Maximum size |
mandatory |
bool |
True |
If True, must be built (no binary indicator) |
effects_per_size |
dict[str, float] |
{} |
Effect cost per unit size (e.g., €/MW) |
effects_fixed |
dict[str, float] |
{} |
Fixed effect cost if built (optional only) |