Skip to content

Commit 7f351ec

Browse files
committed
1. Critical Bug Fix - structure.py:219-229
- Fixed weights property normalization to handle scalars/lists properly - Added zero-sum guard to prevent division by zero - Now always aligns weights to model coords before normalizing 2. Documentation - flow_system.py - Added documentation for the weight_of_last_period parameter in the FlowSystem class docstring 3. Code Quality - interface.py - Refactored deprecated kwargs handling to use instance method instead of awkward static call pattern - Removed unnecessary import and cleaner implementation 4. Parameter Name Updates - Test Files Updated deprecated parameter names in all test files: - tests/test_scenarios.py - tests/test_functional.py - tests/conftest.py - tests/test_flow.py - tests/test_linear_converter.py 5. Parameter Name Updates - Documentation Updated parameter names in: - docs/user-guide/mathematical-notation/features/OnOffParameters.md 6. Parameter Name Updates - Examples Updated parameter names in: - examples/02_Complex/complex_example.py 7. Enhanced CHANGELOG.md Added comprehensive migration guidance including: - Clear explanation of weighting behavior for _over_periods constraints - Concrete example showing per-period vs over-periods differences - Removal timeline (version 4.0.0) for deprecated parameters - Simple migration instructions All deprecated parameters: - on_hours_total_min → on_hours_min - on_hours_total_max → on_hours_max - switch_on_total_max → switch_on_max - flow_hours_total_min → flow_hours_min - flow_hours_total_max → flow_hours_max The codebase is now fully updated with consistent naming, proper documentation, and backward compatibility maintained through deprecation warnings!
1 parent ac03159 commit 7f351ec

13 files changed

Lines changed: 69 additions & 157 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
6161
- `Effect`: Added `minimum_over_periods` and `maximum_over_periods` for weighted sum constraints across all periods (complements existing per-period `minimum_total`/`maximum_total`)
6262
- `Flow`: Added `flow_hours_max_over_periods` and `flow_hours_min_over_periods` for weighted sum constraints across all periods
6363
64+
**Important**: Constraints with the `_over_periods` suffix compute weighted sums across all periods using the weights specified in `FlowSystem.weights` or `Effect.weights`. Per-period constraints (without the suffix) apply separately to each individual period.
65+
66+
**Example**:
67+
```python
68+
# Per-period constraint: limits apply to EACH period individually
69+
# With periods=[2020, 2030, 2040], this creates 3 separate constraints
70+
effect = fx.Effect('costs', maximum_total=1000) # ≤1000 in 2020 AND ≤1000 in 2030 AND ≤1000 in 2040
71+
72+
# Over-periods constraint: limits apply to WEIGHTED SUM across ALL periods
73+
# With periods=[2020, 2030, 2040] and weights=[0.5, 0.3, 0.2], this creates 1 constraint
74+
effect = fx.Effect('costs', maximum_over_periods=1000) # 0.5×costs₂₀₂₀ + 0.3×costs₂₀₃₀ + 0.2×costs₂₀₄₀ ≤ 1000
75+
```
76+
6477
### ♻️ Changed
6578
6679
- **Parameter naming consistency**: Established consistent naming pattern for constraint parameters across `Effect`, `Flow`, and `OnOffParameters`:
@@ -81,7 +94,12 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
8194
- **Flow parameters**: `flow_hours_total_max`, `flow_hours_total_min` (use `flow_hours_max`, `flow_hours_min`)
8295
- **OnOffParameters**: `on_hours_total_max`, `on_hours_total_min`, `switch_on_total_max` (use `on_hours_max`, `on_hours_min`, `switch_on_max`)
8396
84-
All deprecated parameter names continue to work with deprecation warnings for backward compatibility. Additional property aliases have been added internally to handle various naming variations that may have been used.
97+
All deprecated parameter names continue to work with deprecation warnings for backward compatibility. **Deprecated names will be removed in version 4.0.0.** Please update your code to use the new parameter names. Additional property aliases have been added internally to handle various naming variations that may have been used.
98+
99+
**Migration**: Simply rename parameters by removing `_total` from the middle:
100+
- `flow_hours_total_max` → `flow_hours_max`
101+
- `on_hours_total_min` → `on_hours_min`
102+
- `switch_on_total_max` → `switch_on_max`
85103
86104
### 🐛 Fixed
87105

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,10 @@ For equipment with OnOffParameters, the complete constraint system includes:
237237
**Key Parameters:**
238238
- `effects_per_switch_on`: Costs per startup event
239239
- `effects_per_running_hour`: Costs per hour of operation
240-
- `on_hours_total_min`, `on_hours_total_max`: Total runtime bounds
240+
- `on_hours_min`, `on_hours_max`: Total runtime bounds
241241
- `consecutive_on_hours_min`, `consecutive_on_hours_max`: Consecutive runtime bounds
242242
- `consecutive_off_hours_min`, `consecutive_off_hours_max`: Consecutive shutdown bounds
243-
- `switch_on_total_max`: Maximum number of startups
243+
- `switch_on_max`: Maximum number of startups
244244
- `force_switch_on`: Create switch variables even without limits (for tracking)
245245

246246
See the [`OnOffParameters`][flixopt.interface.OnOffParameters] API documentation for complete parameter list and usage examples.
@@ -265,7 +265,7 @@ power_plant = OnOffParameters(
265265
effects_per_running_hour={'fixed_om': 125}, # €125/hour while running
266266
consecutive_on_hours_min=8, # Minimum 8-hour run
267267
consecutive_off_hours_min=4, # 4-hour cooling period
268-
on_hours_total_max=6000, # Annual limit
268+
on_hours_max=6000, # Annual limit
269269
)
270270
```
271271

@@ -276,7 +276,7 @@ batch_reactor = OnOffParameters(
276276
consecutive_on_hours_min=12, # 12-hour minimum batch
277277
consecutive_on_hours_max=24, # 24-hour maximum batch
278278
consecutive_off_hours_min=6, # Cleaning time
279-
switch_on_total_max=200, # Max 200 batches
279+
switch_on_max=200, # Max 200 batches
280280
)
281281
```
282282

@@ -286,7 +286,7 @@ hvac = OnOffParameters(
286286
effects_per_switch_on={'compressor_wear': 0.5},
287287
consecutive_on_hours_min=1, # Prevent short cycling
288288
consecutive_off_hours_min=0.5, # 30-min minimum off
289-
switch_on_total_max=2000, # Limit compressor starts
289+
switch_on_max=2000, # Limit compressor starts
290290
)
291291
```
292292

@@ -296,7 +296,7 @@ backup_gen = OnOffParameters(
296296
effects_per_switch_on={'fuel_priming': 50}, # L diesel
297297
consecutive_on_hours_min=0.5, # 30-min test duration
298298
consecutive_off_hours_max=720, # Test every 30 days
299-
on_hours_total_min=26, # Weekly testing requirement
299+
on_hours_min=26, # Weekly testing requirement
300300
)
301301
```
302302

examples/02_Complex/complex_example.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,15 @@
6868
relative_minimum=5 / 50, # Minimum part load
6969
relative_maximum=1, # Maximum part load
7070
previous_flow_rate=50, # Previous flow rate
71-
flow_hours_total_max=1e6, # Total energy flow limit
71+
flow_hours_max=1e6, # Total energy flow limit
7272
on_off_parameters=fx.OnOffParameters(
73-
on_hours_total_min=0, # Minimum operating hours
74-
on_hours_total_max=1000, # Maximum operating hours
73+
on_hours_min=0, # Minimum operating hours
74+
on_hours_max=1000, # Maximum operating hours
7575
consecutive_on_hours_max=10, # Max consecutive operating hours
7676
consecutive_on_hours_min=np.array([1, 1, 1, 1, 1, 2, 2, 2, 2]), # min consecutive operation hours
7777
consecutive_off_hours_max=10, # Max consecutive off hours
7878
effects_per_switch_on=0.01, # Cost per switch-on
79-
switch_on_total_max=1000, # Max number of starts
79+
switch_on_max=1000, # Max number of starts
8080
),
8181
),
8282
Q_fu=fx.Flow(label='Q_fu', bus='Gas', size=200),

flixopt/effects.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ class Effect(Element):
7878
maximum_invest: Use `maximum_periodic` instead.
7979
minimum_operation_per_hour: Use `minimum_per_hour` instead.
8080
maximum_operation_per_hour: Use `maximum_per_hour` instead.
81-
minimum_total_per_period: Use `minimum_total` instead.
82-
maximum_total_per_period: Use `maximum_total` instead.
83-
minimum: Use `minimum_over_periods` instead.
84-
maximum: Use `maximum_over_periods` instead.
8581
8682
Examples:
8783
Basic cost objective:
@@ -232,20 +228,6 @@ def __init__(
232228
maximum_per_hour = self._handle_deprecated_kwarg(
233229
kwargs, 'maximum_operation_per_hour', 'maximum_per_hour', maximum_per_hour
234230
)
235-
minimum_total = self._handle_deprecated_kwarg(
236-
kwargs, 'minimum_total_per_period', 'minimum_total', minimum_total
237-
)
238-
maximum_total = self._handle_deprecated_kwarg(
239-
kwargs, 'maximum_total_per_period', 'maximum_total', maximum_total
240-
)
241-
minimum_over_periods = self._handle_deprecated_kwarg(
242-
kwargs, 'minimum', 'minimum_over_periods', minimum_over_periods
243-
)
244-
maximum_over_periods = self._handle_deprecated_kwarg(
245-
kwargs, 'maximum', 'maximum_over_periods', maximum_over_periods
246-
)
247-
248-
# Validate any remaining unexpected kwargs
249231
self._validate_kwargs(kwargs)
250232

251233
# Set attributes directly

flixopt/elements.py

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -593,46 +593,6 @@ def size_is_fixed(self) -> bool:
593593
return False if (isinstance(self.size, InvestParameters) and self.size.fixed_size is None) else True
594594

595595
# Backwards compatible properties (deprecated)
596-
@property
597-
def flow_hours_per_period_max(self):
598-
"""DEPRECATED: Use 'flow_hours_max' property instead."""
599-
warnings.warn(
600-
"Property 'flow_hours_per_period_max' is deprecated. Use 'flow_hours_max' instead.",
601-
DeprecationWarning,
602-
stacklevel=2,
603-
)
604-
return self.flow_hours_max
605-
606-
@flow_hours_per_period_max.setter
607-
def flow_hours_per_period_max(self, value):
608-
"""DEPRECATED: Use 'flow_hours_max' property instead."""
609-
warnings.warn(
610-
"Property 'flow_hours_per_period_max' is deprecated. Use 'flow_hours_max' instead.",
611-
DeprecationWarning,
612-
stacklevel=2,
613-
)
614-
self.flow_hours_max = value
615-
616-
@property
617-
def flow_hours_per_period_min(self):
618-
"""DEPRECATED: Use 'flow_hours_min' property instead."""
619-
warnings.warn(
620-
"Property 'flow_hours_per_period_min' is deprecated. Use 'flow_hours_min' instead.",
621-
DeprecationWarning,
622-
stacklevel=2,
623-
)
624-
return self.flow_hours_min
625-
626-
@flow_hours_per_period_min.setter
627-
def flow_hours_per_period_min(self, value):
628-
"""DEPRECATED: Use 'flow_hours_min' property instead."""
629-
warnings.warn(
630-
"Property 'flow_hours_per_period_min' is deprecated. Use 'flow_hours_min' instead.",
631-
DeprecationWarning,
632-
stacklevel=2,
633-
)
634-
self.flow_hours_min = value
635-
636596
@property
637597
def flow_hours_total_max(self):
638598
"""DEPRECATED: Use 'flow_hours_max' property instead."""
@@ -673,46 +633,6 @@ def flow_hours_total_min(self, value):
673633
)
674634
self.flow_hours_min = value
675635

676-
@property
677-
def total_flow_hours_max(self):
678-
"""DEPRECATED: Use 'flow_hours_max_over_periods' property instead."""
679-
warnings.warn(
680-
"Property 'total_flow_hours_max' is deprecated. Use 'flow_hours_max_over_periods' instead.",
681-
DeprecationWarning,
682-
stacklevel=2,
683-
)
684-
return self.flow_hours_max_over_periods
685-
686-
@total_flow_hours_max.setter
687-
def total_flow_hours_max(self, value):
688-
"""DEPRECATED: Use 'flow_hours_max_over_periods' property instead."""
689-
warnings.warn(
690-
"Property 'total_flow_hours_max' is deprecated. Use 'flow_hours_max_over_periods' instead.",
691-
DeprecationWarning,
692-
stacklevel=2,
693-
)
694-
self.flow_hours_max_over_periods = value
695-
696-
@property
697-
def total_flow_hours_min(self):
698-
"""DEPRECATED: Use 'flow_hours_min_over_periods' property instead."""
699-
warnings.warn(
700-
"Property 'total_flow_hours_min' is deprecated. Use 'flow_hours_min_over_periods' instead.",
701-
DeprecationWarning,
702-
stacklevel=2,
703-
)
704-
return self.flow_hours_min_over_periods
705-
706-
@total_flow_hours_min.setter
707-
def total_flow_hours_min(self, value):
708-
"""DEPRECATED: Use 'flow_hours_min_over_periods' property instead."""
709-
warnings.warn(
710-
"Property 'total_flow_hours_min' is deprecated. Use 'flow_hours_min_over_periods' instead.",
711-
DeprecationWarning,
712-
stacklevel=2,
713-
)
714-
self.flow_hours_min_over_periods = value
715-
716636
def _format_invest_params(self, params: InvestParameters) -> str:
717637
"""Format InvestParameters for display."""
718638
return f'size: {params.format_for_repr()}'
@@ -755,16 +675,16 @@ def _do_modeling(self):
755675
weight_per_period = self._model.flow_system.weight_per_period
756676
if weight_per_period is not None:
757677
# Calculate weighted sum over all periods
758-
weighted_total_flow_hours = (self.total_flow_hours * weight_per_period).sum('period')
678+
weighted_flow_hours_over_periods = (self.total_flow_hours * weight_per_period).sum('period')
759679
else:
760680
# No period weights defined, use unweighted sum
761-
weighted_total_flow_hours = self.total_flow_hours.sum('period')
681+
weighted_flow_hours_over_periods = self.total_flow_hours.sum('period')
762682

763683
# Create tracking variable for the weighted sum
764684
ModelingPrimitives.expression_tracking_variable(
765685
model=self,
766-
name=f'{self.label_full}|total_flow_hours_over_periods',
767-
tracked_expression=weighted_total_flow_hours,
686+
name=f'{self.label_full}|flow_hours_over_periods',
687+
tracked_expression=weighted_flow_hours_over_periods,
768688
bounds=(
769689
self.element.flow_hours_min_over_periods
770690
if self.element.flow_hours_min_over_periods is not None
@@ -774,7 +694,7 @@ def _do_modeling(self):
774694
else None,
775695
),
776696
coords=['scenario'],
777-
short_name='total_flow_hours_over_periods',
697+
short_name='flow_hours_over_periods',
778698
)
779699

780700
# Load factor constraints

flixopt/flow_system.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class FlowSystem(Interface, CompositeContainerMixin[Element]):
5151
hours_of_previous_timesteps: Duration of previous timesteps. If None, computed from the first time interval.
5252
Can be a scalar (all previous timesteps have same duration) or array (different durations).
5353
Used to calculate previous values (e.g., consecutive_on_hours).
54+
weight_of_last_period: Weight/duration of the last period. If None, computed from the last period interval.
55+
Used for calculating sums over periods in multi-period models.
5456
weights: The weights of each period and scenario. If None, all scenarios have the same weight (normalized to 1).
5557
Its recommended to normalize the weights to sum up to 1.
5658
scenario_independent_sizes: Controls whether investment sizes are equalized across scenarios.

flixopt/interface.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,20 +1273,10 @@ def __init__(
12731273
**kwargs,
12741274
):
12751275
# Handle deprecated parameters
1276-
from .structure import Element # Import here to avoid circular import
1277-
1278-
on_hours_min = Element._handle_deprecated_kwarg(
1279-
None, kwargs, 'on_hours_total_min', 'on_hours_min', on_hours_min
1280-
)
1281-
on_hours_max = Element._handle_deprecated_kwarg(
1282-
None, kwargs, 'on_hours_total_max', 'on_hours_max', on_hours_max
1283-
)
1284-
switch_on_max = Element._handle_deprecated_kwarg(
1285-
None, kwargs, 'switch_on_total_max', 'switch_on_max', switch_on_max
1286-
)
1287-
# Validate any remaining unexpected kwargs
1288-
if kwargs:
1289-
raise TypeError(f'OnOffParameters got unexpected keyword arguments: {", ".join(kwargs.keys())}')
1276+
on_hours_min = self._handle_deprecated_kwarg(kwargs, 'on_hours_total_min', 'on_hours_min', on_hours_min)
1277+
on_hours_max = self._handle_deprecated_kwarg(kwargs, 'on_hours_total_max', 'on_hours_max', on_hours_max)
1278+
switch_on_max = self._handle_deprecated_kwarg(kwargs, 'switch_on_total_max', 'switch_on_max', switch_on_max)
1279+
self._validate_kwargs(kwargs)
12901280

12911281
self.effects_per_switch_on = effects_per_switch_on if effects_per_switch_on is not None else {}
12921282
self.effects_per_running_hour = effects_per_running_hour if effects_per_running_hour is not None else {}

flixopt/structure.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,16 +216,20 @@ def get_coords(
216216
return xr.Coordinates(coords) if coords else None
217217

218218
@property
219-
def weights(self) -> int | xr.DataArray:
219+
def weights(self) -> xr.DataArray:
220220
"""Returns the weights of the FlowSystem. Normalizes to 1 if normalize_weights is True"""
221-
if self.flow_system.weights is not None:
222-
weights = self.flow_system.weights
223-
if self.normalize_weights:
224-
# Normalize weights to sum to 1
225-
weights = weights / weights.sum()
221+
weights = self.flow_system.fit_to_model_coords(
222+
name='weights',
223+
data=self.flow_system.weights if self.flow_system.weights is not None else 1,
224+
dims=['period', 'scenario'],
225+
)
226+
if not self.normalize_weights:
226227
return weights
227228

228-
return self.flow_system.fit_to_model_coords('weights', 1, dims=['period', 'scenario'])
229+
total = weights.sum()
230+
if np.isclose(total, 0):
231+
raise ValueError('FlowSystemModel.weights: weights sum to 0; cannot normalize.')
232+
return weights / total
229233

230234
def __repr__(self) -> str:
231235
"""

tests/conftest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,15 @@ def complex():
165165
effects_of_investment_per_size={'costs': 10, 'PE': 2},
166166
),
167167
on_off_parameters=fx.OnOffParameters(
168-
on_hours_total_min=0,
169-
on_hours_total_max=1000,
168+
on_hours_min=0,
169+
on_hours_max=1000,
170170
consecutive_on_hours_max=10,
171171
consecutive_on_hours_min=1,
172172
consecutive_off_hours_max=10,
173173
effects_per_switch_on=0.01,
174-
switch_on_total_max=1000,
174+
switch_on_max=1000,
175175
),
176-
flow_hours_total_max=1e6,
176+
flow_hours_max=1e6,
177177
),
178178
Q_fu=fx.Flow('Q_fu', bus='Gas', size=200, relative_minimum=0, relative_maximum=1),
179179
)

tests/test_flow.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ def test_flow(self, basic_flow_system_linopy_coords, coords_config):
4848
size=100,
4949
relative_minimum=np.linspace(0, 0.5, timesteps.size),
5050
relative_maximum=np.linspace(0.5, 1, timesteps.size),
51-
flow_hours_total_max=1000,
52-
flow_hours_total_min=10,
51+
flow_hours_max=1000,
52+
flow_hours_min=10,
5353
load_factor_min=0.1,
5454
load_factor_max=0.9,
5555
)
@@ -976,7 +976,7 @@ def test_switch_on_constraints(self, basic_flow_system_linopy_coords, coords_con
976976
bus='Fernwärme',
977977
size=100,
978978
on_off_parameters=fx.OnOffParameters(
979-
switch_on_total_max=5, # Maximum 5 startups
979+
switch_on_max=5, # Maximum 5 startups
980980
effects_per_switch_on={'costs': 100}, # 100 EUR startup cost
981981
),
982982
)
@@ -1038,8 +1038,8 @@ def test_on_hours_limits(self, basic_flow_system_linopy_coords, coords_config):
10381038
bus='Fernwärme',
10391039
size=100,
10401040
on_off_parameters=fx.OnOffParameters(
1041-
on_hours_total_min=20, # Minimum 20 hours of operation
1042-
on_hours_total_max=100, # Maximum 100 hours of operation
1041+
on_hours_min=20, # Minimum 20 hours of operation
1042+
on_hours_max=100, # Maximum 100 hours of operation
10431043
),
10441044
)
10451045

0 commit comments

Comments
 (0)