From ea0b2a019efa70c805f745fe7f078a099ab71ee1 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 26 Sep 2025 00:41:28 +0200 Subject: [PATCH 1/3] Change .optional to .mandatory --- flixopt/elements.py | 4 +-- flixopt/features.py | 10 ++++---- flixopt/interface.py | 60 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/flixopt/elements.py b/flixopt/elements.py index 22256b636..1f4048a2c 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -489,7 +489,7 @@ def size_is_fixed(self) -> bool: @property def invest_is_optional(self) -> bool: # Wenn kein InvestParameters existiert: # Investment ist nicht optional -> Keine Variable --> False - return False if (isinstance(self.size, InvestParameters) and not self.size.optional) else True + return False if (isinstance(self.size, InvestParameters) and self.size.mandatory) else True class FlowModel(ElementModel): @@ -650,7 +650,7 @@ def flow_rate_lower_bound(self) -> NumericData: if self.element.on_off_parameters is not None: return 0 if isinstance(self.element.size, InvestParameters): - if self.element.size.optional: + if not self.element.size.mandatory: return 0 return self.flow_rate_lower_bound_relative * self.element.size.minimum_size return self.flow_rate_lower_bound_relative * self.element.size diff --git a/flixopt/features.py b/flixopt/features.py index 5528917e0..985250a51 100644 --- a/flixopt/features.py +++ b/flixopt/features.py @@ -46,7 +46,7 @@ def __init__( self.parameters = parameters def do_modeling(self): - if self.parameters.fixed_size and not self.parameters.optional: + if self.parameters.fixed_size and self.parameters.mandatory: self.size = self.add( self._model.add_variables( lower=self.parameters.fixed_size, upper=self.parameters.fixed_size, name=f'{self.label_full}|size' @@ -56,15 +56,15 @@ def do_modeling(self): else: self.size = self.add( self._model.add_variables( - lower=0 if self.parameters.optional else self.parameters.minimum_size, + lower=0 if not self.parameters.mandatory else self.parameters.minimum_size, upper=self.parameters.maximum_size, name=f'{self.label_full}|size', ), 'size', ) - # Optional - if self.parameters.optional: + # Optional (not mandatory) + if not self.parameters.mandatory: self.is_invested = self.add( self._model.add_variables(binary=True, name=f'{self.label_full}|is_invested'), 'is_invested' ) @@ -89,7 +89,7 @@ def _create_shares(self): target='invest', ) - if self.parameters.divest_effects != {} and self.parameters.optional: + if self.parameters.divest_effects != {} and not self.parameters.mandatory: # share: divest_effects - isInvested * divest_effects self._model.effects.add_share_to_effects( name=self.label_of_element, diff --git a/flixopt/interface.py b/flixopt/interface.py index e72e28b90..a713e759d 100644 --- a/flixopt/interface.py +++ b/flixopt/interface.py @@ -655,9 +655,12 @@ class InvestParameters(Interface): maximum_size: Upper bound for continuous sizing decisions. Defaults to a large value (CONFIG.modeling.BIG) representing unlimited capacity. Ignored when fixed_size is specified. - optional: Controls whether investment is required. When True (default), - optimization can choose not to invest. When False, forces investment + mandatory: Controls whether investment is required. When True, forces investment to occur (useful for mandatory upgrades or replacement decisions). + When False (default), optimization can choose not to invest. + optional: DEPRECATED. Use `mandatory` instead. Controls whether investment is required. + When True (default), optimization can choose not to invest. When False, forces + investment to occur. This parameter is maintained for backwards compatibility. fix_effects: Fixed costs incurred once if investment is made, regardless of size. Dictionary mapping effect names to values (e.g., {'cost': 10000, 'CO2_construction': 500}). @@ -687,7 +690,7 @@ class InvestParameters(Interface): ```python solar_investment = InvestParameters( fixed_size=100, # 100 kW system (binary decision) - optional=True, + mandatory=False, # Investment is optional fix_effects={ 'cost': 25000, # Installation and permitting costs 'CO2': -50000, # Avoided emissions over lifetime @@ -705,7 +708,7 @@ class InvestParameters(Interface): battery_investment = InvestParameters( minimum_size=10, # Minimum viable system size (kWh) maximum_size=1000, # Maximum installable capacity - optional=True, + mandatory=False, # Investment is optional fix_effects={ 'cost': 5000, # Grid connection and control system 'installation_time': 2, # Days for fixed components @@ -737,7 +740,7 @@ class InvestParameters(Interface): boiler_replacement = InvestParameters( minimum_size=50, maximum_size=200, - optional=True, # Can choose not to replace + mandatory=False, # Can choose not to replace fix_effects={ 'cost': 15000, # Installation costs 'disruption': 3, # Days of downtime @@ -821,7 +824,8 @@ def __init__( fixed_size: int | float | None = None, minimum_size: int | float | None = None, maximum_size: int | float | None = None, - optional: bool = True, # Investition ist weglassbar + mandatory: bool | None = None, + optional: bool | None = None, # DEPRECATED: use mandatory instead fix_effects: EffectValuesUserScalar | None = None, specific_effects: EffectValuesUserScalar | None = None, # costs per Flow-Unit/Storage-Size/... piecewise_effects: PiecewiseEffects | None = None, @@ -830,17 +834,59 @@ def __init__( self.fix_effects: EffectValuesUserScalar = fix_effects or {} self.divest_effects: EffectValuesUserScalar = divest_effects or {} self.fixed_size = fixed_size - self.optional = optional self.specific_effects: EffectValuesUserScalar = specific_effects or {} self.piecewise_effects = piecewise_effects self._minimum_size = minimum_size if minimum_size is not None else CONFIG.modeling.EPSILON self._maximum_size = maximum_size if maximum_size is not None else CONFIG.modeling.BIG # default maximum + # Handle backwards compatibility between mandatory and optional + if mandatory is not None and optional is not None: + raise ValueError("Cannot specify both 'mandatory' and 'optional' parameters. Use 'mandatory' instead.") + elif mandatory is not None: + self._mandatory = mandatory + elif optional is not None: + import warnings + + warnings.warn( + "Parameter 'optional' is deprecated. Use 'mandatory=not optional' instead.", + DeprecationWarning, + stacklevel=2, + ) + self._mandatory = not optional + else: + self._mandatory = False # Default: not mandatory (i.e., optional) + def transform_data(self, flow_system: FlowSystem): self.fix_effects = flow_system.effects.create_effect_values_dict(self.fix_effects) self.divest_effects = flow_system.effects.create_effect_values_dict(self.divest_effects) self.specific_effects = flow_system.effects.create_effect_values_dict(self.specific_effects) + @property + def mandatory(self) -> bool: + """Controls whether investment is required.""" + return self._mandatory + + @mandatory.setter + def mandatory(self, value: bool): + """Set whether investment is required.""" + self._mandatory = value + + @property + def optional(self) -> bool: + """DEPRECATED: Use 'mandatory' property instead. Returns the opposite of 'mandatory'.""" + import warnings + + warnings.warn("Property 'optional' is deprecated. Use 'mandatory' instead.", DeprecationWarning, stacklevel=2) + return not self._mandatory + + @optional.setter + def optional(self, value: bool): + """DEPRECATED: Use 'mandatory' property instead. Sets the opposite of the given value to 'mandatory'.""" + import warnings + + warnings.warn("Property 'optional' is deprecated. Use 'mandatory' instead.", DeprecationWarning, stacklevel=2) + self._mandatory = not value + @property def minimum_size(self): return self.fixed_size or self._minimum_size From 823b221f7397c2a220a29e6fd00f0544bc80e4b4 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:52:47 +0200 Subject: [PATCH 2/3] Change .optional to .mandatory --- flixopt/elements.py | 9 +++++++++ flixopt/interface.py | 46 +++++++++++--------------------------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/flixopt/elements.py b/flixopt/elements.py index 1f4048a2c..80b9384dc 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -489,8 +489,17 @@ def size_is_fixed(self) -> bool: @property def invest_is_optional(self) -> bool: # Wenn kein InvestParameters existiert: # Investment ist nicht optional -> Keine Variable --> False + warnings.warn( + "The 'invest_is_optional' property is deprecated. Use 'invest_is_mandatory' instead.", + DeprecationWarning, + stacklevel=2, + ) return False if (isinstance(self.size, InvestParameters) and self.size.mandatory) else True + @property + def invest_is_mandatory(self) -> bool: + return False if (isinstance(self.size, InvestParameters) and not self.size.mandatory) else True + class FlowModel(ElementModel): def __init__(self, model: SystemModel, element: Flow): diff --git a/flixopt/interface.py b/flixopt/interface.py index a713e759d..aa113acc3 100644 --- a/flixopt/interface.py +++ b/flixopt/interface.py @@ -6,6 +6,7 @@ from __future__ import annotations import logging +import warnings from typing import TYPE_CHECKING from .config import CONFIG @@ -658,9 +659,7 @@ class InvestParameters(Interface): mandatory: Controls whether investment is required. When True, forces investment to occur (useful for mandatory upgrades or replacement decisions). When False (default), optimization can choose not to invest. - optional: DEPRECATED. Use `mandatory` instead. Controls whether investment is required. - When True (default), optimization can choose not to invest. When False, forces - investment to occur. This parameter is maintained for backwards compatibility. + optional: DEPRECATED. Use `mandatory` instead. Opposite of `mandatory`. fix_effects: Fixed costs incurred once if investment is made, regardless of size. Dictionary mapping effect names to values (e.g., {'cost': 10000, 'CO2_construction': 500}). @@ -824,12 +823,13 @@ def __init__( fixed_size: int | float | None = None, minimum_size: int | float | None = None, maximum_size: int | float | None = None, - mandatory: bool | None = None, - optional: bool | None = None, # DEPRECATED: use mandatory instead + mandatory: bool = False, fix_effects: EffectValuesUserScalar | None = None, specific_effects: EffectValuesUserScalar | None = None, # costs per Flow-Unit/Storage-Size/... piecewise_effects: PiecewiseEffects | None = None, divest_effects: EffectValuesUserScalar | None = None, + # Backwards compatibility - deprecated parameter + optional: bool | None = None, ): self.fix_effects: EffectValuesUserScalar = fix_effects or {} self.divest_effects: EffectValuesUserScalar = divest_effects or {} @@ -838,54 +838,30 @@ def __init__( self.piecewise_effects = piecewise_effects self._minimum_size = minimum_size if minimum_size is not None else CONFIG.modeling.EPSILON self._maximum_size = maximum_size if maximum_size is not None else CONFIG.modeling.BIG # default maximum + self.mandatory = mandatory - # Handle backwards compatibility between mandatory and optional - if mandatory is not None and optional is not None: - raise ValueError("Cannot specify both 'mandatory' and 'optional' parameters. Use 'mandatory' instead.") - elif mandatory is not None: - self._mandatory = mandatory - elif optional is not None: - import warnings - - warnings.warn( - "Parameter 'optional' is deprecated. Use 'mandatory=not optional' instead.", - DeprecationWarning, - stacklevel=2, - ) - self._mandatory = not optional - else: - self._mandatory = False # Default: not mandatory (i.e., optional) + # Handle backwards compatibility for optional parameter + if optional is not None: + self.optional = optional def transform_data(self, flow_system: FlowSystem): self.fix_effects = flow_system.effects.create_effect_values_dict(self.fix_effects) self.divest_effects = flow_system.effects.create_effect_values_dict(self.divest_effects) self.specific_effects = flow_system.effects.create_effect_values_dict(self.specific_effects) - @property - def mandatory(self) -> bool: - """Controls whether investment is required.""" - return self._mandatory - - @mandatory.setter - def mandatory(self, value: bool): - """Set whether investment is required.""" - self._mandatory = value - @property def optional(self) -> bool: """DEPRECATED: Use 'mandatory' property instead. Returns the opposite of 'mandatory'.""" import warnings warnings.warn("Property 'optional' is deprecated. Use 'mandatory' instead.", DeprecationWarning, stacklevel=2) - return not self._mandatory + return not self.mandatory @optional.setter def optional(self, value: bool): """DEPRECATED: Use 'mandatory' property instead. Sets the opposite of the given value to 'mandatory'.""" - import warnings - warnings.warn("Property 'optional' is deprecated. Use 'mandatory' instead.", DeprecationWarning, stacklevel=2) - self._mandatory = not value + self.mandatory = not value @property def minimum_size(self): From 4eefd294db6796445e8435d5d2eeec316411feab Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:06:39 +0200 Subject: [PATCH 3/3] Remove not needed properties --- flixopt/elements.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/flixopt/elements.py b/flixopt/elements.py index 80b9384dc..f9dcf0cee 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -486,20 +486,6 @@ def size_is_fixed(self) -> bool: # Wenn kein InvestParameters existiert --> True; Wenn Investparameter, den Wert davon nehmen return False if (isinstance(self.size, InvestParameters) and self.size.fixed_size is None) else True - @property - def invest_is_optional(self) -> bool: - # Wenn kein InvestParameters existiert: # Investment ist nicht optional -> Keine Variable --> False - warnings.warn( - "The 'invest_is_optional' property is deprecated. Use 'invest_is_mandatory' instead.", - DeprecationWarning, - stacklevel=2, - ) - return False if (isinstance(self.size, InvestParameters) and self.size.mandatory) else True - - @property - def invest_is_mandatory(self) -> bool: - return False if (isinstance(self.size, InvestParameters) and not self.size.mandatory) else True - class FlowModel(ElementModel): def __init__(self, model: SystemModel, element: Flow):