Skip to content

Commit 7ac41c2

Browse files
authored
Improve effects parameter naming in InvestParameters (#389)
* FIrst Try * Improve deprecation * Update usage of deprectated parameters * Improve None handling * Add extra kwargs handling * Improve deprecation * Use custom method for kwargs * Add deprecation method * Apply deprecation method to other classes * Apply to effects.py as well * Update usage of deprectaed parameters * Update CHANGELOG.md * Update Docs
1 parent 834f44a commit 7ac41c2

19 files changed

Lines changed: 585 additions & 233 deletions

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,24 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
101101
- 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.
102102
- 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.
103103
- 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.
104-
- Effect parameters renamed:
104+
- **InvestParameters** parameters renamed for improved clarity around investment and retirement effects:
105+
- `fix_effects` → `effects_of_investment`
106+
- `specific_effects` → `effects_of_investment_per_size`
107+
- `divest_effects` → `effects_of_retirement`
108+
- `piecewise_effects` → `piecewise_effects_of_investment`
109+
- **Effect** parameters renamed:
105110
- `minimum_investment` → `minimum_periodic`
106111
- `maximum_investment` → `maximum_periodic`
107112
- `minimum_operation` → `minimum_temporal`
108113
- `maximum_operation` → `maximum_temporal`
109114
- `minimum_operation_per_hour` → `minimum_per_hour`
110115
- `maximum_operation_per_hour` → `maximum_per_hour`
116+
- **Component** parameters renamed:
117+
- `Source.source` → `Source.outputs`
118+
- `Sink.sink` → `Sink.inputs`
119+
- `SourceAndSink.source` → `SourceAndSink.outputs`
120+
- `SourceAndSink.sink` → `SourceAndSink.inputs`
121+
- `SourceAndSink.prevent_simultaneous_sink_and_source` → `SourceAndSink.prevent_simultaneous_flow_rates`
111122
112123
### 🔥 Removed
113124
@@ -137,6 +148,7 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
137148
138149
### 👷 Development
139150
151+
- **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%.
140152
- FlowSystem data management simplified - removed `time_series_collection` pattern in favor of direct timestep properties
141153
- 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.
142154
- Added new module `.modeling` that contains Modelling primitives and utilities

docs/user-guide/mathematical-notation/features/InvestParameters.md

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,25 @@ See [Piecewise](../features/Piecewise.md) for detailed mathematical formulation.
137137

138138
---
139139

140-
### Divestment Effects
140+
### Retirement Effects
141141

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

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

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

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

156156
**Examples:**
157157
- Demolition or disposal costs
158+
- Decommissioning expenses
158159
- Contractual penalties for not investing
159160
- Opportunity costs or lost revenues
160161

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

167168
$$\label{eq:invest_total_effects}
168-
E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{divest}}
169+
E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}}
169170
$$
170171

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

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

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

@@ -261,20 +262,20 @@ battery_investment = InvestParameters(
261262
minimum_size=10, # kWh
262263
maximum_size=1000,
263264
optional=True,
264-
fix_effects={'cost': 5000}, # Grid connection
265-
specific_effects={'cost': 600}, # €600/kWh
265+
effects_of_investment={'cost': 5000}, # Grid connection
266+
effects_of_investment_per_size={'cost': 600}, # €600/kWh
266267
)
267268
```
268269

269-
### With Divestment Costs (Replacement)
270+
### With Retirement Costs (Replacement)
270271
```python
271272
boiler_replacement = InvestParameters(
272273
minimum_size=50, # kW
273274
maximum_size=200,
274275
optional=True,
275-
fix_effects={'cost': 15000},
276-
specific_effects={'cost': 400},
277-
divest_effects={'cost': 8000}, # Demolition if not replaced
276+
effects_of_investment={'cost': 15000},
277+
effects_of_investment_per_size={'cost': 400},
278+
effects_of_retirement={'cost': 8000}, # Demolition if not replaced
278279
)
279280
```
280281

@@ -283,7 +284,7 @@ boiler_replacement = InvestParameters(
283284
battery_investment = InvestParameters(
284285
minimum_size=10,
285286
maximum_size=1000,
286-
piecewise_effects=PiecewiseEffects(
287+
piecewise_effects_of_investment=PiecewiseEffects(
287288
piecewise_origin=Piecewise([
288289
Piece(0, 100), # Small
289290
Piece(100, 500), # Medium

examples/01_Simple/simple_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
label='Storage',
6565
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1000),
6666
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1000),
67-
capacity_in_flow_hours=fx.InvestParameters(fix_effects=20, fixed_size=30, optional=False),
67+
capacity_in_flow_hours=fx.InvestParameters(effects_of_investment=20, fixed_size=30, optional=False),
6868
initial_charge_state=0, # Initial storage state: empty
6969
relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]),
7070
relative_maximum_final_charge_state=0.8,

examples/02_Complex/complex_example.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@
5757
label='Q_th', # Thermal output
5858
bus='Fernwärme', # Linked bus
5959
size=fx.InvestParameters(
60-
fix_effects=1000, # Fixed investment costs
60+
effects_of_investment=1000, # Fixed investment costs
6161
fixed_size=50, # Fixed size
6262
optional=False, # Forced investment
63-
specific_effects={Costs.label: 10, PE.label: 2}, # Specific costs
63+
effects_of_investment_per_size={Costs.label: 10, PE.label: 2}, # Specific costs
6464
),
6565
load_factor_max=1.0, # Maximum load factor (50 kW)
6666
load_factor_min=0.1, # Minimum load factor (5 kW)
@@ -130,7 +130,7 @@
130130
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1e4),
131131
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1e4),
132132
capacity_in_flow_hours=fx.InvestParameters(
133-
piecewise_effects=segmented_investment_effects, # Investment effects
133+
piecewise_effects_of_investment=segmented_investment_effects, # Investment effects
134134
optional=False, # Forced investment
135135
minimum_size=0,
136136
maximum_size=1000, # Optimizing between 0 and 1000 kWh

examples/04_Scenarios/scenario_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
label='Storage',
7878
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1000),
7979
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1000),
80-
capacity_in_flow_hours=fx.InvestParameters(fix_effects=20, fixed_size=30, optional=False),
80+
capacity_in_flow_hours=fx.InvestParameters(effects_of_investment=20, fixed_size=30, optional=False),
8181
initial_charge_state=0, # Initial storage state: empty
8282
relative_maximum_charge_state=np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]) * 0.01,
8383
relative_maximum_final_charge_state=0.8,

examples/05_Two-stage-optimization/two_stage_optimization.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
Q_fu=fx.Flow(
4949
label='Q_fu',
5050
bus='Gas',
51-
size=fx.InvestParameters(specific_effects={'costs': 1_000}, minimum_size=10, maximum_size=500),
51+
size=fx.InvestParameters(
52+
effects_of_investment_per_size={'costs': 1_000}, minimum_size=10, maximum_size=500
53+
),
5254
relative_minimum=0.2,
5355
previous_flow_rate=20,
5456
on_off_parameters=fx.OnOffParameters(effects_per_switch_on=300),
@@ -66,15 +68,17 @@
6668
Q_fu=fx.Flow(
6769
'Q_fu',
6870
bus='Kohle',
69-
size=fx.InvestParameters(specific_effects={'costs': 3_000}, minimum_size=10, maximum_size=500),
71+
size=fx.InvestParameters(
72+
effects_of_investment_per_size={'costs': 3_000}, minimum_size=10, maximum_size=500
73+
),
7074
relative_minimum=0.3,
7175
previous_flow_rate=100,
7276
),
7377
),
7478
fx.Storage(
7579
'Speicher',
7680
capacity_in_flow_hours=fx.InvestParameters(
77-
minimum_size=10, maximum_size=1000, specific_effects={'costs': 60}
81+
minimum_size=10, maximum_size=1000, effects_of_investment_per_size={'costs': 60}
7882
),
7983
initial_charge_state='lastValueOfSim',
8084
eta_charge=1,

flixopt/components.py

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,36 +1060,16 @@ def __init__(
10601060
meta_data: dict | None = None,
10611061
**kwargs,
10621062
):
1063-
source = kwargs.pop('source', None)
1064-
sink = kwargs.pop('sink', None)
1065-
prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
1066-
if source is not None:
1067-
warnings.warn(
1068-
'The use of the "source" argument is deprecated. Use the "outputs" argument instead.',
1069-
DeprecationWarning,
1070-
stacklevel=2,
1071-
)
1072-
if outputs is not None:
1073-
raise ValueError('Either source or outputs can be specified, but not both.')
1074-
outputs = [source]
1075-
1076-
if sink is not None:
1077-
warnings.warn(
1078-
'The use of the "sink" argument is deprecated. Use the "inputs" argument instead.',
1079-
DeprecationWarning,
1080-
stacklevel=2,
1081-
)
1082-
if inputs is not None:
1083-
raise ValueError('Either sink or inputs can be specified, but not both.')
1084-
inputs = [sink]
1085-
1086-
if prevent_simultaneous_sink_and_source is not None:
1087-
warnings.warn(
1088-
'The use of the "prevent_simultaneous_sink_and_source" argument is deprecated. Use the "prevent_simultaneous_flow_rates" argument instead.',
1089-
DeprecationWarning,
1090-
stacklevel=2,
1091-
)
1092-
prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
1063+
# Handle deprecated parameters using centralized helper
1064+
outputs = self._handle_deprecated_kwarg(kwargs, 'source', 'outputs', outputs, transform=lambda x: [x])
1065+
inputs = self._handle_deprecated_kwarg(kwargs, 'sink', 'inputs', inputs, transform=lambda x: [x])
1066+
prevent_simultaneous_flow_rates = self._handle_deprecated_kwarg(
1067+
kwargs,
1068+
'prevent_simultaneous_sink_and_source',
1069+
'prevent_simultaneous_flow_rates',
1070+
prevent_simultaneous_flow_rates,
1071+
check_conflict=False,
1072+
)
10931073

10941074
# Validate any remaining unexpected kwargs
10951075
self._validate_kwargs(kwargs)
@@ -1215,16 +1195,8 @@ def __init__(
12151195
prevent_simultaneous_flow_rates: bool = False,
12161196
**kwargs,
12171197
):
1218-
source = kwargs.pop('source', None)
1219-
if source is not None:
1220-
warnings.warn(
1221-
'The use of the "source" argument is deprecated. Use the "outputs" argument instead.',
1222-
DeprecationWarning,
1223-
stacklevel=2,
1224-
)
1225-
if outputs is not None:
1226-
raise ValueError('Either source or outputs can be specified, but not both.')
1227-
outputs = [source]
1198+
# Handle deprecated parameter using centralized helper
1199+
outputs = self._handle_deprecated_kwarg(kwargs, 'source', 'outputs', outputs, transform=lambda x: [x])
12281200

12291201
# Validate any remaining unexpected kwargs
12301202
self._validate_kwargs(kwargs)
@@ -1346,16 +1318,8 @@ def __init__(
13461318
Note:
13471319
The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
13481320
"""
1349-
sink = kwargs.pop('sink', None)
1350-
if sink is not None:
1351-
warnings.warn(
1352-
'The use of the "sink" argument is deprecated. Use the "inputs" argument instead.',
1353-
DeprecationWarning,
1354-
stacklevel=2,
1355-
)
1356-
if inputs is not None:
1357-
raise ValueError('Either sink or inputs can be specified, but not both.')
1358-
inputs = [sink]
1321+
# Handle deprecated parameter using centralized helper
1322+
inputs = self._handle_deprecated_kwarg(kwargs, 'sink', 'inputs', inputs, transform=lambda x: [x])
13591323

13601324
# Validate any remaining unexpected kwargs
13611325
self._validate_kwargs(kwargs)

flixopt/effects.py

Lines changed: 15 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -187,84 +187,21 @@ def __init__(
187187
self.share_from_temporal: TemporalEffectsUser = share_from_temporal if share_from_temporal is not None else {}
188188
self.share_from_periodic: PeriodicEffectsUser = share_from_periodic if share_from_periodic is not None else {}
189189

190-
# Handle backwards compatibility for deprecated parameters
191-
# Extract deprecated parameters from kwargs
192-
minimum_operation = kwargs.pop('minimum_operation', None)
193-
maximum_operation = kwargs.pop('maximum_operation', None)
194-
minimum_invest = kwargs.pop('minimum_invest', None)
195-
maximum_invest = kwargs.pop('maximum_invest', None)
196-
minimum_operation_per_hour = kwargs.pop('minimum_operation_per_hour', None)
197-
maximum_operation_per_hour = kwargs.pop('maximum_operation_per_hour', None)
198-
199-
# Handle minimum_temporal
200-
if minimum_operation is not None:
201-
warnings.warn(
202-
"Parameter 'minimum_operation' is deprecated. Use 'minimum_temporal' instead.",
203-
DeprecationWarning,
204-
stacklevel=2,
205-
)
206-
if minimum_temporal is not None:
207-
raise ValueError('Either minimum_operation or minimum_temporal can be specified, but not both.')
208-
minimum_temporal = minimum_operation
209-
210-
# Handle maximum_temporal
211-
if maximum_operation is not None:
212-
warnings.warn(
213-
"Parameter 'maximum_operation' is deprecated. Use 'maximum_temporal' instead.",
214-
DeprecationWarning,
215-
stacklevel=2,
216-
)
217-
if maximum_temporal is not None:
218-
raise ValueError('Either maximum_operation or maximum_temporal can be specified, but not both.')
219-
maximum_temporal = maximum_operation
220-
221-
# Handle minimum_periodic
222-
if minimum_invest is not None:
223-
warnings.warn(
224-
"Parameter 'minimum_invest' is deprecated. Use 'minimum_periodic' instead.",
225-
DeprecationWarning,
226-
stacklevel=2,
227-
)
228-
if minimum_periodic is not None:
229-
raise ValueError('Either minimum_invest or minimum_periodic can be specified, but not both.')
230-
minimum_periodic = minimum_invest
231-
232-
# Handle maximum_periodic
233-
if maximum_invest is not None:
234-
warnings.warn(
235-
"Parameter 'maximum_invest' is deprecated. Use 'maximum_periodic' instead.",
236-
DeprecationWarning,
237-
stacklevel=2,
238-
)
239-
if maximum_periodic is not None:
240-
raise ValueError('Either maximum_invest or maximum_periodic can be specified, but not both.')
241-
maximum_periodic = maximum_invest
242-
243-
# Handle minimum_per_hour
244-
if minimum_operation_per_hour is not None:
245-
warnings.warn(
246-
"Parameter 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead.",
247-
DeprecationWarning,
248-
stacklevel=2,
249-
)
250-
if minimum_per_hour is not None:
251-
raise ValueError(
252-
'Either minimum_operation_per_hour or minimum_per_hour can be specified, but not both.'
253-
)
254-
minimum_per_hour = minimum_operation_per_hour
255-
256-
# Handle maximum_per_hour
257-
if maximum_operation_per_hour is not None:
258-
warnings.warn(
259-
"Parameter 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead.",
260-
DeprecationWarning,
261-
stacklevel=2,
262-
)
263-
if maximum_per_hour is not None:
264-
raise ValueError(
265-
'Either maximum_operation_per_hour or maximum_per_hour can be specified, but not both.'
266-
)
267-
maximum_per_hour = maximum_operation_per_hour
190+
# Handle backwards compatibility for deprecated parameters using centralized helper
191+
minimum_temporal = self._handle_deprecated_kwarg(
192+
kwargs, 'minimum_operation', 'minimum_temporal', minimum_temporal
193+
)
194+
maximum_temporal = self._handle_deprecated_kwarg(
195+
kwargs, 'maximum_operation', 'maximum_temporal', maximum_temporal
196+
)
197+
minimum_periodic = self._handle_deprecated_kwarg(kwargs, 'minimum_invest', 'minimum_periodic', minimum_periodic)
198+
maximum_periodic = self._handle_deprecated_kwarg(kwargs, 'maximum_invest', 'maximum_periodic', maximum_periodic)
199+
minimum_per_hour = self._handle_deprecated_kwarg(
200+
kwargs, 'minimum_operation_per_hour', 'minimum_per_hour', minimum_per_hour
201+
)
202+
maximum_per_hour = self._handle_deprecated_kwarg(
203+
kwargs, 'maximum_operation_per_hour', 'maximum_per_hour', maximum_per_hour
204+
)
268205

269206
# Validate any remaining unexpected kwargs
270207
self._validate_kwargs(kwargs)

0 commit comments

Comments
 (0)