diff --git a/flixOpt/elements.py b/flixOpt/elements.py index c9e2a0575..e2402d608 100644 --- a/flixOpt/elements.py +++ b/flixOpt/elements.py @@ -307,7 +307,6 @@ def do_modeling(self, system_model: SystemModel): 'flow_rate', self, system_model.nr_of_time_steps, - fixed_value=self.fixed_relative_flow_rate, lower_bound=self.absolute_flow_rate_bounds[0] if self.element.on_off_parameters is None else 0, upper_bound=self.absolute_flow_rate_bounds[1] if self.element.on_off_parameters is None else None, previous_values=self.element.previous_flow_rate, @@ -328,7 +327,9 @@ def do_modeling(self, system_model: SystemModel): self.element.size, self.flow_rate, self.relative_flow_rate_bounds, - fixed_relative_profile=self.fixed_relative_flow_rate, + fixed_relative_profile=(None + if self.element.fixed_relative_profile is None + else self.element.fixed_relative_profile.active_data), on_variable=self._on.on if self._on is not None else None, ) self._investment.do_modeling(system_model) @@ -392,24 +393,17 @@ def with_investment(self) -> bool: """Checks if the element's size is investment-driven.""" return isinstance(self.element.size, InvestParameters) - @property - def fixed_relative_flow_rate(self) -> Optional[np.ndarray]: - """Returns a fixed flow rate if defined by the element.""" - if self.element.fixed_relative_profile is not None: - return self.element.fixed_relative_profile.active_data - return None - @property def absolute_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]: - """Returns absolute flow rate bounds. Iportant for OnOffModel""" + """Returns absolute flow rate bounds. Important for OnOffModel""" rel_min, rel_max = self.relative_flow_rate_bounds size = self.element.size - if self.with_investment: - if size.fixed_size is not None: - return rel_min * size.fixed_size, rel_max * size.fixed_size - return rel_min * size.minimum_size, rel_max * size.maximum_size - else: + if not self.with_investment: return rel_min * size, rel_max * size + if size.fixed_size is not None: + return rel_min * size.fixed_size, rel_max * size.fixed_size + return rel_min * size.minimum_size, rel_max * size.maximum_size + @property def relative_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]: @@ -417,10 +411,7 @@ def relative_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]: fixed_profile = self.element.fixed_relative_profile if fixed_profile is None: return self.element.relative_minimum.active_data, self.element.relative_maximum.active_data - return ( - np.minimum(fixed_profile.active_data, self.element.relative_minimum.active_data), - np.maximum(fixed_profile.active_data, self.element.relative_maximum.active_data), - ) + return fixed_profile.active_data, fixed_profile.active_data class BusModel(ElementModel): diff --git a/flixOpt/features.py b/flixOpt/features.py index ce88913ec..f05d7d29e 100644 --- a/flixOpt/features.py +++ b/flixOpt/features.py @@ -309,7 +309,7 @@ def _add_on_constraints(self, system_model: SystemModel, time_indices: Union[lis # eq: sum( Leistung(t,i)) - sum(Leistung_max(i)) * On(t) <= 0 # --> damit Gleichungswerte nicht zu groß werden, noch durch nr_of_flows geteilt: # eq: sum( Leistung(t,i) / nr_of_flows ) - sum(Leistung_max(i)) / nr_of_flows * On(t) <= 0 - absolute_maximum: Numeric = 0 + absolute_maximum: Numeric = 0.0 for variable, bounds in zip(self._defining_variables, self._defining_bounds, strict=False): eq_on_2.add_summand(variable, 1 / nr_of_defining_variables, time_indices) absolute_maximum += bounds[ diff --git a/tests/test_functional.py b/tests/test_functional.py index 2d2e7ba50..238a776d3 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -370,6 +370,61 @@ def test_optional_invest(self): err_msg='"Boiler__Q_th__IsInvested" does not have the right value', ) + def test_fixed_relative_profile(self): + self.flow_system = self.create_model(self.datetime_array) + self.flow_system.add_elements( + fx.linear_converters.Boiler( + 'Boiler', + 0.5, + Q_fu=fx.Flow('Q_fu', bus=self.get_element('Gas')), + Q_th=fx.Flow( + 'Q_th', + bus=self.get_element('Fernwärme'), + size=fx.InvestParameters(optional=True, minimum_size=40, fix_effects=10, specific_effects=1), + ), + ), + fx.linear_converters.Boiler( + 'Boiler_optional', + 0.5, + Q_fu=fx.Flow('Q_fu', bus=self.get_element('Gas')), + Q_th=fx.Flow( + 'Q_th', + bus=self.get_element('Fernwärme'), + size=fx.InvestParameters(optional=True, minimum_size=50, fix_effects=10, specific_effects=1), + ), + ), + ) + self.flow_system.add_elements( + fx.Source( + 'Wärmequelle', + source=fx.Flow('Q_th', + bus=self.get_element('Fernwärme'), + fixed_relative_profile=np.linspace(0, 5, len(self.datetime_array)), + size=fx.InvestParameters(optional=False, minimum_size=2, maximum_size=5), + ) + ) + ) + self.get_element('Fernwärme').excess_penalty_per_flow_hour = 1e5 + + self.solve_and_load(self.flow_system) + source = self.get_element('Wärmequelle') + assert_allclose( + source.source.model.flow_rate.result, + np.linspace(0, 5, len(self.datetime_array)) * source.source.model._investment.size.result, + rtol=self.mip_gap, + atol=1e-10, + err_msg='The total costs does not have the right value', + ) + assert_allclose( + source.source.model._investment.size.result, + 2, + rtol=self.mip_gap, + atol=1e-10, + err_msg='The total costs does not have the right value', + ) + + + class TestOnOff(BaseTest): """