Skip to content
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,24 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
- The `active_timesteps` parameter of `Calculation` is deprecated and will be removed in a future version. Use the new `sel(time=...)` method on the FlowSystem instead.
- The assignment of Bus Objects to Flow.bus is deprecated and will be removed in a future version. Use the label of the Bus instead.
- The usage of Effects objects in Dicts to assign shares to Effects is deprecated and will be removed in a future version. Use the label of the Effect instead.
- Effect parameters renamed:
- **InvestParameters** parameters renamed for improved clarity around investment and retirement effects:
- `fix_effects` → `effects_of_investment`
- `specific_effects` → `effects_of_investment_per_size`
- `divest_effects` → `effects_of_retirement`
- `piecewise_effects` → `piecewise_effects_of_investment`
- **Effect** parameters renamed:
- `minimum_investment` → `minimum_periodic`
- `maximum_investment` → `maximum_periodic`
- `minimum_operation` → `minimum_temporal`
- `maximum_operation` → `maximum_temporal`
- `minimum_operation_per_hour` → `minimum_per_hour`
- `maximum_operation_per_hour` → `maximum_per_hour`
- **Component** parameters renamed:
- `Source.source` → `Source.outputs`
- `Sink.sink` → `Sink.inputs`
- `SourceAndSink.source` → `SourceAndSink.outputs`
- `SourceAndSink.sink` → `SourceAndSink.inputs`
- `SourceAndSink.prevent_simultaneous_sink_and_source` → `SourceAndSink.prevent_simultaneous_flow_rates`

### 🔥 Removed

Expand Down Expand Up @@ -137,6 +148,7 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir

### 👷 Development

- **Centralized deprecation pattern**: Added `_handle_deprecated_kwarg()` helper method to `Interface` base class that provides reusable deprecation handling with consistent warnings, conflict detection, and optional value transformation. Applied across 5 classes (InvestParameters, Source, Sink, SourceAndSink, Effect) reducing deprecation boilerplate by 72%.
- FlowSystem data management simplified - removed `time_series_collection` pattern in favor of direct timestep properties
- Change modeling hierarchy to allow for more flexibility in future development. This leads to minimal changes in the access and creation of Submodels and their variables.
- Added new module `.modeling` that contains Modelling primitives and utilities
Expand Down
45 changes: 23 additions & 22 deletions docs/user-guide/mathematical-notation/features/InvestParameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,25 @@ See [Piecewise](../features/Piecewise.md) for detailed mathematical formulation.

---

### Divestment Effects
### Retirement Effects

Costs incurred if investment is NOT made:
Effects incurred if investment is NOT made (when retiring/not replacing existing equipment):

$$\label{eq:invest_divest_effects}
E_{e,\text{divest}} = (1 - s_\text{invest}) \cdot \text{divest}_e
$$\label{eq:invest_retirement_effects}
E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e
$$

With:
- $E_{e,\text{divest}}$ being the divestment contribution to effect $e$
- $\text{divest}_e$ being the divestment effect value
- $E_{e,\text{retirement}}$ being the retirement contribution to effect $e$
- $\text{retirement}_e$ being the retirement effect value

**Behavior:**
- $s_\text{invest} = 0$: divestment effects are incurred
- $s_\text{invest} = 1$: no divestment effects
- $s_\text{invest} = 0$: retirement effects are incurred
- $s_\text{invest} = 1$: no retirement effects

**Examples:**
- Demolition or disposal costs
- Decommissioning expenses
- Contractual penalties for not investing
- Opportunity costs or lost revenues

Expand All @@ -165,7 +166,7 @@ With:
The total contribution to effect $e$ from an investment is:

$$\label{eq:invest_total_effects}
E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{divest}}
E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}}
$$

Effects integrate into the overall system effects as described in [Effects, Penalty & Objective](../effects-penalty-objective.md).
Expand Down Expand Up @@ -228,10 +229,10 @@ $$
- `fixed_size`: For binary investments (mutually exclusive with continuous sizing)
- `minimum_size`, `maximum_size`: For continuous sizing
- `optional`: Whether investment can be skipped
- `fix_effects`: Fixed costs dictionary
- `specific_effects`: Per-unit costs dictionary
- `piecewise_effects`: Non-linear cost modeling
- `divest_effects`: Costs for not investing
- `effects_of_investment`: Fixed effects incurred when investing (replaces deprecated `fix_effects`)
- `effects_of_investment_per_size`: Per-unit effects proportional to size (replaces deprecated `specific_effects`)
- `piecewise_effects_of_investment`: Non-linear effect modeling (replaces deprecated `piecewise_effects`)
- `effects_of_retirement`: Effects for not investing (replaces deprecated `divest_effects`)

See the [`InvestParameters`][flixopt.interface.InvestParameters] API documentation for complete parameter list and usage examples.

Expand All @@ -250,8 +251,8 @@ See the [`InvestParameters`][flixopt.interface.InvestParameters] API documentati
solar_investment = InvestParameters(
fixed_size=100, # 100 kW system
optional=True,
fix_effects={'cost': 25000}, # Installation costs
specific_effects={'cost': 1200}, # €1200/kW
effects_of_investment={'cost': 25000}, # Installation costs
effects_of_investment_per_size={'cost': 1200}, # €1200/kW
)
```

Expand All @@ -261,20 +262,20 @@ battery_investment = InvestParameters(
minimum_size=10, # kWh
maximum_size=1000,
optional=True,
fix_effects={'cost': 5000}, # Grid connection
specific_effects={'cost': 600}, # €600/kWh
effects_of_investment={'cost': 5000}, # Grid connection
effects_of_investment_per_size={'cost': 600}, # €600/kWh
)
```

### With Divestment Costs (Replacement)
### With Retirement Costs (Replacement)
```python
boiler_replacement = InvestParameters(
minimum_size=50, # kW
maximum_size=200,
optional=True,
fix_effects={'cost': 15000},
specific_effects={'cost': 400},
divest_effects={'cost': 8000}, # Demolition if not replaced
effects_of_investment={'cost': 15000},
effects_of_investment_per_size={'cost': 400},
effects_of_retirement={'cost': 8000}, # Demolition if not replaced
)
```

Expand All @@ -283,7 +284,7 @@ boiler_replacement = InvestParameters(
battery_investment = InvestParameters(
minimum_size=10,
maximum_size=1000,
piecewise_effects=PiecewiseEffects(
piecewise_effects_of_investment=PiecewiseEffects(
piecewise_origin=Piecewise([
Piece(0, 100), # Small
Piece(100, 500), # Medium
Expand Down
2 changes: 1 addition & 1 deletion examples/01_Simple/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
label='Storage',
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1000),
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1000),
capacity_in_flow_hours=fx.InvestParameters(fix_effects=20, fixed_size=30, optional=False),
capacity_in_flow_hours=fx.InvestParameters(effects_of_investment=20, fixed_size=30, optional=False),
initial_charge_state=0, # Initial storage state: empty
relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]),
relative_maximum_final_charge_state=0.8,
Expand Down
6 changes: 3 additions & 3 deletions examples/02_Complex/complex_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
label='Q_th', # Thermal output
bus='Fernwärme', # Linked bus
size=fx.InvestParameters(
fix_effects=1000, # Fixed investment costs
effects_of_investment=1000, # Fixed investment costs
fixed_size=50, # Fixed size
optional=False, # Forced investment
specific_effects={Costs.label: 10, PE.label: 2}, # Specific costs
effects_of_investment_per_size={Costs.label: 10, PE.label: 2}, # Specific costs
),
load_factor_max=1.0, # Maximum load factor (50 kW)
load_factor_min=0.1, # Minimum load factor (5 kW)
Expand Down Expand Up @@ -130,7 +130,7 @@
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1e4),
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1e4),
capacity_in_flow_hours=fx.InvestParameters(
piecewise_effects=segmented_investment_effects, # Investment effects
piecewise_effects_of_investment=segmented_investment_effects, # Investment effects
optional=False, # Forced investment
minimum_size=0,
maximum_size=1000, # Optimizing between 0 and 1000 kWh
Expand Down
2 changes: 1 addition & 1 deletion examples/04_Scenarios/scenario_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
label='Storage',
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1000),
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1000),
capacity_in_flow_hours=fx.InvestParameters(fix_effects=20, fixed_size=30, optional=False),
capacity_in_flow_hours=fx.InvestParameters(effects_of_investment=20, fixed_size=30, optional=False),
initial_charge_state=0, # Initial storage state: empty
relative_maximum_charge_state=np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]) * 0.01,
relative_maximum_final_charge_state=0.8,
Expand Down
10 changes: 7 additions & 3 deletions examples/05_Two-stage-optimization/two_stage_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@
Q_fu=fx.Flow(
label='Q_fu',
bus='Gas',
size=fx.InvestParameters(specific_effects={'costs': 1_000}, minimum_size=10, maximum_size=500),
size=fx.InvestParameters(
effects_of_investment_per_size={'costs': 1_000}, minimum_size=10, maximum_size=500
),
relative_minimum=0.2,
previous_flow_rate=20,
on_off_parameters=fx.OnOffParameters(effects_per_switch_on=300),
Expand All @@ -66,15 +68,17 @@
Q_fu=fx.Flow(
'Q_fu',
bus='Kohle',
size=fx.InvestParameters(specific_effects={'costs': 3_000}, minimum_size=10, maximum_size=500),
size=fx.InvestParameters(
effects_of_investment_per_size={'costs': 3_000}, minimum_size=10, maximum_size=500
),
relative_minimum=0.3,
previous_flow_rate=100,
),
),
fx.Storage(
'Speicher',
capacity_in_flow_hours=fx.InvestParameters(
minimum_size=10, maximum_size=1000, specific_effects={'costs': 60}
minimum_size=10, maximum_size=1000, effects_of_investment_per_size={'costs': 60}
),
initial_charge_state='lastValueOfSim',
eta_charge=1,
Expand Down
64 changes: 14 additions & 50 deletions flixopt/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -1060,36 +1060,16 @@ def __init__(
meta_data: dict | None = None,
**kwargs,
):
source = kwargs.pop('source', None)
sink = kwargs.pop('sink', None)
prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
if source is not None:
warnings.warn(
'The use of the "source" argument is deprecated. Use the "outputs" argument instead.',
DeprecationWarning,
stacklevel=2,
)
if outputs is not None:
raise ValueError('Either source or outputs can be specified, but not both.')
outputs = [source]

if sink is not None:
warnings.warn(
'The use of the "sink" argument is deprecated. Use the "inputs" argument instead.',
DeprecationWarning,
stacklevel=2,
)
if inputs is not None:
raise ValueError('Either sink or inputs can be specified, but not both.')
inputs = [sink]

if prevent_simultaneous_sink_and_source is not None:
warnings.warn(
'The use of the "prevent_simultaneous_sink_and_source" argument is deprecated. Use the "prevent_simultaneous_flow_rates" argument instead.',
DeprecationWarning,
stacklevel=2,
)
prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
# Handle deprecated parameters using centralized helper
outputs = self._handle_deprecated_kwarg(kwargs, 'source', 'outputs', outputs, transform=lambda x: [x])
inputs = self._handle_deprecated_kwarg(kwargs, 'sink', 'inputs', inputs, transform=lambda x: [x])
prevent_simultaneous_flow_rates = self._handle_deprecated_kwarg(
kwargs,
'prevent_simultaneous_sink_and_source',
'prevent_simultaneous_flow_rates',
prevent_simultaneous_flow_rates,
check_conflict=False,
)

# Validate any remaining unexpected kwargs
self._validate_kwargs(kwargs)
Expand Down Expand Up @@ -1215,16 +1195,8 @@ def __init__(
prevent_simultaneous_flow_rates: bool = False,
**kwargs,
):
source = kwargs.pop('source', None)
if source is not None:
warnings.warn(
'The use of the "source" argument is deprecated. Use the "outputs" argument instead.',
DeprecationWarning,
stacklevel=2,
)
if outputs is not None:
raise ValueError('Either source or outputs can be specified, but not both.')
outputs = [source]
# Handle deprecated parameter using centralized helper
outputs = self._handle_deprecated_kwarg(kwargs, 'source', 'outputs', outputs, transform=lambda x: [x])

# Validate any remaining unexpected kwargs
self._validate_kwargs(kwargs)
Expand Down Expand Up @@ -1346,16 +1318,8 @@ def __init__(
Note:
The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
"""
sink = kwargs.pop('sink', None)
if sink is not None:
warnings.warn(
'The use of the "sink" argument is deprecated. Use the "inputs" argument instead.',
DeprecationWarning,
stacklevel=2,
)
if inputs is not None:
raise ValueError('Either sink or inputs can be specified, but not both.')
inputs = [sink]
# Handle deprecated parameter using centralized helper
inputs = self._handle_deprecated_kwarg(kwargs, 'sink', 'inputs', inputs, transform=lambda x: [x])

# Validate any remaining unexpected kwargs
self._validate_kwargs(kwargs)
Expand Down
93 changes: 15 additions & 78 deletions flixopt/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,84 +187,21 @@ def __init__(
self.share_from_temporal: TemporalEffectsUser = share_from_temporal if share_from_temporal is not None else {}
self.share_from_periodic: PeriodicEffectsUser = share_from_periodic if share_from_periodic is not None else {}

# Handle backwards compatibility for deprecated parameters
# Extract deprecated parameters from kwargs
minimum_operation = kwargs.pop('minimum_operation', None)
maximum_operation = kwargs.pop('maximum_operation', None)
minimum_invest = kwargs.pop('minimum_invest', None)
maximum_invest = kwargs.pop('maximum_invest', None)
minimum_operation_per_hour = kwargs.pop('minimum_operation_per_hour', None)
maximum_operation_per_hour = kwargs.pop('maximum_operation_per_hour', None)

# Handle minimum_temporal
if minimum_operation is not None:
warnings.warn(
"Parameter 'minimum_operation' is deprecated. Use 'minimum_temporal' instead.",
DeprecationWarning,
stacklevel=2,
)
if minimum_temporal is not None:
raise ValueError('Either minimum_operation or minimum_temporal can be specified, but not both.')
minimum_temporal = minimum_operation

# Handle maximum_temporal
if maximum_operation is not None:
warnings.warn(
"Parameter 'maximum_operation' is deprecated. Use 'maximum_temporal' instead.",
DeprecationWarning,
stacklevel=2,
)
if maximum_temporal is not None:
raise ValueError('Either maximum_operation or maximum_temporal can be specified, but not both.')
maximum_temporal = maximum_operation

# Handle minimum_periodic
if minimum_invest is not None:
warnings.warn(
"Parameter 'minimum_invest' is deprecated. Use 'minimum_periodic' instead.",
DeprecationWarning,
stacklevel=2,
)
if minimum_periodic is not None:
raise ValueError('Either minimum_invest or minimum_periodic can be specified, but not both.')
minimum_periodic = minimum_invest

# Handle maximum_periodic
if maximum_invest is not None:
warnings.warn(
"Parameter 'maximum_invest' is deprecated. Use 'maximum_periodic' instead.",
DeprecationWarning,
stacklevel=2,
)
if maximum_periodic is not None:
raise ValueError('Either maximum_invest or maximum_periodic can be specified, but not both.')
maximum_periodic = maximum_invest

# Handle minimum_per_hour
if minimum_operation_per_hour is not None:
warnings.warn(
"Parameter 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead.",
DeprecationWarning,
stacklevel=2,
)
if minimum_per_hour is not None:
raise ValueError(
'Either minimum_operation_per_hour or minimum_per_hour can be specified, but not both.'
)
minimum_per_hour = minimum_operation_per_hour

# Handle maximum_per_hour
if maximum_operation_per_hour is not None:
warnings.warn(
"Parameter 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead.",
DeprecationWarning,
stacklevel=2,
)
if maximum_per_hour is not None:
raise ValueError(
'Either maximum_operation_per_hour or maximum_per_hour can be specified, but not both.'
)
maximum_per_hour = maximum_operation_per_hour
# Handle backwards compatibility for deprecated parameters using centralized helper
minimum_temporal = self._handle_deprecated_kwarg(
kwargs, 'minimum_operation', 'minimum_temporal', minimum_temporal
)
maximum_temporal = self._handle_deprecated_kwarg(
kwargs, 'maximum_operation', 'maximum_temporal', maximum_temporal
)
minimum_periodic = self._handle_deprecated_kwarg(kwargs, 'minimum_invest', 'minimum_periodic', minimum_periodic)
maximum_periodic = self._handle_deprecated_kwarg(kwargs, 'maximum_invest', 'maximum_periodic', maximum_periodic)
minimum_per_hour = self._handle_deprecated_kwarg(
kwargs, 'minimum_operation_per_hour', 'minimum_per_hour', minimum_per_hour
)
maximum_per_hour = self._handle_deprecated_kwarg(
kwargs, 'maximum_operation_per_hour', 'maximum_per_hour', maximum_per_hour
)

# Validate any remaining unexpected kwargs
self._validate_kwargs(kwargs)
Expand Down
Loading