Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions flixopt/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +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
return False if (isinstance(self.size, InvestParameters) and not self.size.optional) else True


class FlowModel(ElementModel):
def __init__(self, model: SystemModel, element: Flow):
Expand Down Expand Up @@ -650,7 +645,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
Expand Down
10 changes: 5 additions & 5 deletions flixopt/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
)
Expand All @@ -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,
Expand Down
36 changes: 29 additions & 7 deletions flixopt/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

import logging
import warnings
from typing import TYPE_CHECKING

from .config import CONFIG
Expand Down Expand Up @@ -655,9 +656,10 @@ 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. 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}).
Expand Down Expand Up @@ -687,7 +689,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
Expand All @@ -705,7 +707,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
Expand Down Expand Up @@ -737,7 +739,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
Expand Down Expand Up @@ -821,26 +823,46 @@ 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 = 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 {}
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
self.mandatory = mandatory

# 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 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'."""
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
Expand Down