diff --git a/src/mesido/asset_sizing_mixin.py b/src/mesido/asset_sizing_mixin.py index 6ad9c1d3..6f6bb1b3 100644 --- a/src/mesido/asset_sizing_mixin.py +++ b/src/mesido/asset_sizing_mixin.py @@ -794,18 +794,22 @@ def pre(self): # Making the variables for max size - def _make_max_size_var(name, lb, ub, nominal): + def _make_max_size_var(name: str, lb: float, ub: float, nominal: float) -> None: + """ + Creates max_size variables for the asset with the given lower and upper bound and the + nominal value. + + """ asset_max_size_var = f"{name}__max_size" self._asset_max_size_map[name] = asset_max_size_var self.__asset_max_size_var[asset_max_size_var] = ca.MX.sym(asset_max_size_var) self.__asset_max_size_bounds[asset_max_size_var] = (lb, ub) self.__asset_max_size_nominals[asset_max_size_var] = nominal - for asset_name in self.energy_system_components.get("heat_source", []): - ub = bounds[f"{asset_name}.Heat_source"][1] - - # Update bound to account for profile constraint being used instead of 1 value - + def _get_ub_profile_constraint(asset_name: str, profile_name: str, ub: float) -> float: + """ + Returns upper bound to account for profile constraint being used instead of 1 value + """ asset = self.esdl_assets[asset_name] asset_profile_constraints, qty_asset_profile_constraints = get_asset_contraints( self, asset, esdl.ProfileConstraint @@ -819,147 +823,93 @@ def _make_max_size_var(name, lb, ub, nominal): == esdl.UnitEnum.WATT and parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL # Optional asset ): - max_profile = max(self.get_timeseries(f"{asset_name}.maximum_heat_source").values) + max_profile = max(self.get_timeseries(f"{asset_name}.{profile_name}").values) if ub > max_profile: ub = max_profile - - lb = 0.0 if parameters[f"{asset_name}.state"] != AssetStateEnum.ENABLED else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("heat_demand", []): - ub = ( - bounds[f"{asset_name}.Heat_demand"][1] - if not np.isinf(bounds[f"{asset_name}.Heat_demand"][1]) - else bounds[f"{asset_name}.HeatIn.Heat"][1] - ) - # Note that we only enforce the upper bound in state enabled if it was explicitly - # specified for the demand - lb = 0.0 if np.isinf(bounds[f"{asset_name}.Heat_demand"][1]) else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("airco", []): - ub = bounds[f"{asset_name}.Heat_airco"][1] - # Note that we only enforce the upper bound in state enabled if it was explicitly - # specified for the demand - lb = 0.0 if parameters[f"{asset_name}.state"] != 1 else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("cold_demand", []): - ub = ( - bounds[f"{asset_name}.Cold_demand"][1] - if not np.isinf(bounds[f"{asset_name}.Cold_demand"][1]) - else bounds[f"{asset_name}.HeatIn.Heat"][1] - ) - # Note that we only enforce the upper bound in state enabled if it was explicitly - # specified for the demand - lb = 0.0 if np.isinf(bounds[f"{asset_name}.Cold_demand"][1]) else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in [ - *self.energy_system_components.get("ates", []), - ]: - ub = bounds[f"{asset_name}.Heat_ates"][1] - lb = 0.0 if parameters[f"{asset_name}.state"] != AssetStateEnum.ENABLED else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("heat_buffer", []): - ub = ( - max(bounds[f"{asset_name}.Stored_heat"][1].values) - if isinstance(bounds[f"{asset_name}.Stored_heat"][1], Timeseries) - else bounds[f"{asset_name}.Stored_heat"][1] - ) - lb = 0.0 if parameters[f"{asset_name}.state"] != AssetStateEnum.ENABLED else ub - _make_max_size_var( - name=asset_name, - lb=lb, - ub=ub, - nominal=self.variable_nominal(f"{asset_name}.Stored_heat"), + return ub + + def _scalarise_upper_bound(bound_ub): + """ + Scalarises the upperbound from timeserie to the maximum value of the timeserie. + """ + if isinstance(bound_ub, Timeseries): + return np.max(bound_ub.values) + return bound_ub if isinstance(bound_ub, float) else max(bound_ub.values) + + def _demand_ub(asset_name: str, primary_suffix: str, secondary_suffix: str): + """ + Returns the upperbound of a variable with a backup variable name under which it might be + saved. + + """ + demand_ub = bounds[f"{asset_name}.{primary_suffix}"][1] + return ( + demand_ub + if not np.isinf(demand_ub) + else bounds[f"{asset_name}.{secondary_suffix}"][1] ) - for asset_name in [ - *self.energy_system_components.get("heat_exchanger", []), - *self.energy_system_components.get("heat_pump", []), - ]: - ub = bounds[f"{asset_name}.Secondary_heat"][1] - lb = 0.0 if parameters[f"{asset_name}.state"] != AssetStateEnum.ENABLED else ub - _make_max_size_var( - name=asset_name, - lb=lb, - ub=ub, - nominal=self.variable_nominal(f"{asset_name}.Secondary_heat"), - ) - - for asset_name in self.energy_system_components.get("gas_demand", []): - # TODO: add bound value for mass flow rate, used 1.0 for now instead of 0.0 which - # Note that we set the nominal to one to avoid division by zero - ub = 0.0 - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=1.0) - - for asset_name in self.energy_system_components.get("gas_source", []): - # TODO: add bound value for mass flow rate, used 1.0 for now instead of 0.0 which - # Note that we set the nominal to one to avoid division by zero - ub = 0.0 - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=1.0) - - for asset_name in self.energy_system_components.get("gas_tank_storage", []): - ub = bounds[f"{asset_name}.Stored_gas_mass"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("gas_substation", []): - ub = bounds[f"{asset_name}.GasIn.Q"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("compressor", []): - ub = bounds[f"{asset_name}.GasIn.Q"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("electrolyzer", []): - ub = bounds[f"{asset_name}.ElectricityIn.Power"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("electricity_demand", []): - v = bounds[f"{asset_name}.Electricity_demand"][1] - ub = v if not np.isinf(v) else bounds[f"{asset_name}.ElectricityIn.Power"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("transformer", []): - ub = bounds[f"{asset_name}.ElectricityIn.Power"][1] - ub = ub if isinstance(ub, float) else max(ub.values) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("electricity_source", []): - ub = ( - bounds[f"{asset_name}.Electricity_source"][1] - if not isinstance(bounds[f"{asset_name}.Electricity_source"][1], Timeseries) - else np.max(bounds[f"{asset_name}.Electricity_source"][1].values) - ) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - for asset_name in self.energy_system_components.get("electricity_storage", []): - ub = ( - bounds[f"{asset_name}.Stored_electricity"][1] - if not isinstance(bounds[f"{asset_name}.Stored_electricity"][1], Timeseries) - else np.max(bounds[f"{asset_name}.Stored_electricity"][1].values) - ) - lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub - _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) - - # Making the __aggregation_count variable for each asset - for asset_list in self.energy_system_components.values(): + def _make_asset_max_size_vars( + component_type: str, + upper_bound_suffix=None, + upper_bound_suffix_sec=None, + profile_constraint=None, + ): + """ + Creates max_size variables for all assets of the given component type with + upper bound suffixes and profile constraint information. + """ + for asset_name in self.energy_system_components.get(component_type, []): + if upper_bound_suffix_sec is None: + ub_raw = bounds[f"{asset_name}.{upper_bound_suffix}"][1] + else: + ub_raw = _demand_ub(asset_name, upper_bound_suffix, upper_bound_suffix_sec) + ub = _scalarise_upper_bound(ub_raw) + if profile_constraint: + ub = _get_ub_profile_constraint(asset_name, profile_constraint, ub) + lb = 0.0 if parameters[f"{asset_name}.state"] == AssetStateEnum.OPTIONAL else ub + _make_max_size_var(name=asset_name, lb=lb, ub=ub, nominal=ub / 2.0) + + map_asset_type_to_bound_vars = { + "heat_source": { + "upper_bound_suffix": "Heat_source", + "profile_constraint": "maximum_heat_source", + }, + "heat_demand": { + "upper_bound_suffix": "Heat_demand", + "upper_bound_suffix_sec": "HeatIn.Heat", + }, + "cold_demand": { + "upper_bound_suffix": "Cold_demand", + "upper_bound_suffix_sec": "HeatIn.Heat", + }, + "airco": {"upper_bound_suffix": "Heat_airco"}, + "ates": {"upper_bound_suffix": "Heat_ates"}, + "low_temperature_ates": {"upper_bound_suffix": "Heat_ates"}, + "heat_buffer": {"upper_bound_suffix": "Stored_heat"}, + "heat_exchanger": {"upper_bound_suffix": "Secondary_heat"}, + "heat_pump": {"upper_bound_suffix": "Secondary_heat"}, + "gas_tank_storage": {"upper_bound_suffix": "Stored_gas_mass"}, + "gas_substation": {"upper_bound_suffix": "GasIn.Q"}, + "gas_demand": {"upper_bound_suffix": "Gas_demand_mass_flow"}, + "gas_source": {"upper_bound_suffix": "Gas_source_mass_flow"}, + "compressor": {"upper_bound_suffix": "GasIn.Q"}, + "electrolyzer": {"upper_bound_suffix": "ElectricityIn.Power"}, + "electricity_demand": { + "upper_bound_suffix": "Electricity_demand", + "upper_bound_suffix_sec": "ElectricityIn.Power", + }, + "transformer": {"upper_bound_suffix": "ElectricityIn.Power"}, + "electricity_source": {"upper_bound_suffix": "Electricity_source"}, + "electricity_storage": {"upper_bound_suffix": "Stored_electricity"}, + } + + # Create the asset sizing variables for each asset. + for asset_type, asset_list in self.energy_system_components.items(): + if asset_type in map_asset_type_to_bound_vars: + _make_asset_max_size_vars(asset_type, **map_asset_type_to_bound_vars[asset_type]) + elif asset_type not in ["heat_pipe", "gas_pipe", "cable"]: + logger.warning(f"Assets of type {asset_type} is not supported for sizing.") for asset in asset_list: aggr_count_var = f"{asset}_aggregation_count" self._asset_aggregation_count_var_map[asset] = aggr_count_var