From 34bca464e13f50a4a759190149c6cacd8bf9e255 Mon Sep 17 00:00:00 2001 From: Rojer Date: Tue, 9 Apr 2024 17:59:48 +0200 Subject: [PATCH 1/9] parsing the entities, additional logic and parsing for having the carrier at every port in the network and instantiating the required revenue and cost variables --- src/mesido/base_component_type_mixin.py | 8 + src/mesido/component_type_mixin.py | 25 +- src/mesido/esdl/esdl_heat_model.py | 127 +++++++- src/mesido/esdl/esdl_mixin.py | 5 + src/mesido/esdl/esdl_model_base.py | 19 +- src/mesido/esdl/esdl_parser.py | 2 +- src/mesido/financial_mixin.py | 88 ++++++ .../milp/_internal/heat_component.py | 2 + .../milp/electricity/electricity_base.py | 2 + .../component_library/milp/gas/gas_base.py | 3 + .../component_library/milp/heat/heat_port.py | 2 + .../pycml/component_library/milp/heat/node.py | 4 + .../models/heat_exchange/input/timeseries.csv | 4 + .../model/network_with_parties.esdl | 298 ++++++++++++++++++ .../heat_exchange/src/run_heat_exchanger.py | 4 +- 15 files changed, 587 insertions(+), 6 deletions(-) create mode 100644 tests/models/heat_exchange/input/timeseries.csv create mode 100644 tests/models/heat_exchange/model/network_with_parties.esdl diff --git a/src/mesido/base_component_type_mixin.py b/src/mesido/base_component_type_mixin.py index c9a04ff59..4713d741a 100644 --- a/src/mesido/base_component_type_mixin.py +++ b/src/mesido/base_component_type_mixin.py @@ -20,6 +20,14 @@ def energy_system_components(self) -> Dict[str, str]: """ raise NotImplementedError + @property + @abstractmethod + def energy_system_owners(self) -> Dict[str, str]: + """ + This method return a dict with the components structured by owner. + """ + raise NotImplementedError + def energy_system_components_get(self, list_types: list) -> list: components = [] for component_type in list_types: diff --git a/src/mesido/component_type_mixin.py b/src/mesido/component_type_mixin.py index 5b6437085..e64d54c3f 100644 --- a/src/mesido/component_type_mixin.py +++ b/src/mesido/component_type_mixin.py @@ -21,6 +21,7 @@ class ModelicaComponentTypeMixin(BaseComponentTypeMixin): def __init__(self, *args, **kwargs): super().__init__(**kwargs) self.__hn_component_types = None + self.__owners = None def pre(self): """ @@ -314,7 +315,7 @@ def energy_system_components(self) -> Dict[str, Set[str]]: # Find the components in model, detection by string # (name.component_type: type) - component_types = sorted({v for k, v in string_parameters.items()}) + component_types = sorted({v for k, v in string_parameters.items() if "component_type" in k}) components = {} for c in component_types: @@ -326,6 +327,28 @@ def energy_system_components(self) -> Dict[str, Set[str]]: return self.__hn_component_types + @property + def energy_system_owners(self): + if self.__owners is None: + # Create the dictionary once after that it will be available + string_parameters = self.string_parameters(0) + + # Find the components in model, detection by string + # (name.component_type: type) + owners = sorted({v for k, v in string_parameters.items() if "owner" in k}) + + ownership_dict = {} + for c in owners: + ownership_dict[c] = sorted( + {k.split(".")[0] for k, v in string_parameters.items() if v == c} + ) + + ownership_dict.pop("__", None) + + self.__owners = ownership_dict + + return self.__owners + @property def energy_system_topology(self) -> Topology: """ diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index 8bb51ed07..e74f4aff0 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -124,6 +124,79 @@ def get_density(self, asset_name, carrier): density = 6.2 # natural gas at about 8 bar return density * 1.0e3 # to convert from kg/m3 to g/m3 + def get_owner(self, asset): + return dict(owner=asset.attributes["isOwnedBy"].name if asset.attributes["isOwnedBy"] is not None else "NoOwner") + + def get_carrier_id(self, asset, node=False, n=0): + if not node: + if asset.asset_type == "HeatExchange" or asset.asset_type == "HeatPump": + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + if "Prim" in asset.in_ports[0].name: + prim_in_id = carrier["id_number_mapping"] + else: + sec_in_id = carrier["id_number_mapping"] + carrier = asset.global_properties["carriers"][asset.in_ports[1].carrier.id] + if "Prim" in asset.in_ports[1].name: + prim_in_id = carrier["id_number_mapping"] + else: + sec_in_id = carrier["id_number_mapping"] + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + if "Prim" in asset.out_ports[0].name: + prim_out_id = carrier["id_number_mapping"] + else: + sec_out_id = carrier["id_number_mapping"] + carrier = asset.global_properties["carriers"][asset.out_ports[1].carrier.id] + if "Prim" in asset.out_ports[1].name: + prim_out_id = carrier["id_number_mapping"] + else: + sec_out_id = carrier["id_number_mapping"] + ids = dict( + Primary=dict( + HeatIn=dict(carrier_id=prim_in_id), + HeatOut=dict(carrier_id=prim_out_id) + ), + Secondary=dict( + HeatIn=dict(carrier_id=sec_in_id), + HeatOut=dict(carrier_id=sec_out_id) + ), + ) + return ids + else: + ids = dict() + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}In" + ids[port] = dict(carrier_id=id_number) + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}Out" + ids[port] = dict(carrier_id=id_number) + except KeyError: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}In" + ids[port] = dict(carrier_id=id_number) + return ids + else: + ids = dict() + for k in range(0, n): + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}Conn[{k+1}]" + ids[port] = dict(carrier_id=id_number) + except: + pass + try: + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}Conn[{k+1}]" + ids[port] = dict(carrier_id=id_number) + except: + pass + return ids + def get_asset_attribute_value( self, asset: Asset, @@ -307,6 +380,7 @@ def convert_heat_buffer(self, asset: Asset) -> Tuple[Type[HeatBuffer], MODIFIERS **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) return HeatBuffer, modifiers @@ -345,6 +419,7 @@ def convert_heat_demand(self, asset: Asset) -> Tuple[Type[HeatDemand], MODIFIERS **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) return HeatDemand, modifiers @@ -380,6 +455,7 @@ def convert_cold_demand(self, asset: Asset) -> Tuple[Type[ColdDemand], MODIFIERS **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) return ColdDemand, modifiers @@ -420,9 +496,16 @@ def convert_node(self, asset: Asset) -> Tuple[Type[Node], MODIFIERS]: if isinstance(x, esdl.esdl.OutPort): sum_out += len(x.connectedTo) + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + except: + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + modifiers = dict( n=sum_in + sum_out, state=self.get_state(asset), + carrier_id=carrier["id_number_mapping"], + **self.get_owner(asset), ) if isinstance(asset.in_ports[0].carrier, esdl.esdl.GasCommodity) or isinstance( @@ -494,7 +577,9 @@ def convert_heat_pipe( GasOut=bounds_nominals, state=self.get_state(asset), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return GasPipe, modifiers @@ -550,10 +635,13 @@ def convert_heat_pipe( **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) if "T_ground" in asset.attributes.keys(): modifiers["T_ground"] = asset.attributes["T_ground"] + modifiers.update(self.get_carrier_id(asset)) + return HeatPipe, modifiers def convert_pump(self, asset: Asset) -> Tuple[Type[Pump], MODIFIERS]: @@ -594,7 +682,9 @@ def convert_pump(self, asset: Asset) -> Tuple[Type[Pump], MODIFIERS]: state=self.get_state(asset), **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return Pump, modifiers @@ -724,7 +814,9 @@ def convert_heat_exchanger(self, asset: Asset) -> Tuple[Type[HeatExchanger], MOD state=self.get_state(asset), **self._get_cost_figure_modifiers(asset), **params, + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return HeatExchanger, modifiers def convert_heat_pump( @@ -806,7 +898,9 @@ def convert_heat_pump( state=self.get_state(asset), **self._get_cost_figure_modifiers(asset), **params, + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) if len(asset.in_ports) == 2: return HeatPump, modifiers elif len(asset.in_ports) == 3: @@ -879,7 +973,9 @@ def convert_heat_source(self, asset: Asset) -> Tuple[Type[HeatSource], MODIFIERS **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) if asset.asset_type == "GeothermalSource": modifiers["nr_of_doublets"] = asset.attributes["aggregationCount"] @@ -989,7 +1085,9 @@ def convert_ates(self, asset: Asset) -> Tuple[Type[ATES], MODIFIERS]: **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) # if no maxStorageTemperature is specified we assume a "regular" HT ATES model if ( @@ -1062,7 +1160,9 @@ def convert_control_valve(self, asset: Asset) -> Tuple[Type[ControlValve], MODIF state=self.get_state(asset), **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return ControlValve, modifiers @@ -1094,7 +1194,9 @@ def convert_check_valve(self, asset: Asset) -> Tuple[Type[CheckValve], MODIFIERS HeatOut=dict(Hydraulic_power=dict(nominal=q_nominal * 16.0e5)), **self._supply_return_temperature_modifiers(asset), **self._rho_cp_modifiers, + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return CheckValve, modifiers @@ -1134,7 +1236,9 @@ def convert_electricity_demand(self, asset: Asset) -> Tuple[Type[ElectricityDema V=dict(min=min_voltage, nominal=min_voltage), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return ElectricityDemand, modifiers @@ -1170,7 +1274,9 @@ def convert_electricity_source(self, asset: Asset) -> Tuple[Type[ElectricitySour Power=dict(min=0.0, max=max_supply, nominal=max_supply / 2.0), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) if asset.asset_type == "ElectricityProducer": return ElectricitySource, modifiers @@ -1218,7 +1324,9 @@ def convert_electricity_storage( Power=dict(min=-max_discharge, max=max_charge, nominal=max_charge / 2.0), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return ElectricityStorage, modifiers @@ -1257,7 +1365,12 @@ def convert_electricity_node(self, asset: Asset) -> Tuple[Type[ElectricityNode], if isinstance(x, esdl.esdl.OutPort): sum_out += len(x.connectedTo) - modifiers = dict(voltage_nominal=nominal_voltage, n=sum_in + sum_out) + modifiers = dict( + voltage_nominal=nominal_voltage, + n=sum_in + sum_out, + **self.get_owner(asset), + **self.get_carrier_id(asset, node=True, n=sum_in + sum_out) + ) return ElectricityNode, modifiers @@ -1301,7 +1414,9 @@ def convert_electricity_cable(self, asset: Asset) -> Tuple[Type[ElectricityCable Power=dict(min=-max_power, max=max_power, nominal=max_power / 2.0), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return ElectricityCable, modifiers def convert_gas_demand(self, asset: Asset) -> Tuple[Type[GasDemand], MODIFIERS]: @@ -1344,7 +1459,9 @@ def convert_gas_demand(self, asset: Asset) -> Tuple[Type[GasDemand], MODIFIERS]: mass_flow=dict(nominal=density * q_nominal), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return GasDemand, modifiers @@ -1383,7 +1500,9 @@ def convert_gas_source(self, asset: Asset) -> Tuple[Type[GasSource], MODIFIERS]: mass_flow=bounds_nominals_mass_flow, ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return GasSource, modifiers @@ -1450,7 +1569,9 @@ def equations(x): V=dict(min=v_min, nominal=v_min), ), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return Electrolyzer, modifiers @@ -1488,7 +1609,9 @@ def convert_gas_tank_storage(self, asset: Asset) -> Tuple[Type[GasTankStorage], # ) GasIn=dict(Q=dict(nominal=q_nominal), mass_flow=dict(nominal=q_nominal * density)), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return GasTankStorage, modifiers @@ -1519,7 +1642,9 @@ def convert_gas_substation(self, asset: Asset) -> Tuple[Type[GasSubstation], MOD GasIn=dict(Q=dict(nominal=q_nom_in), mass_flow=dict(nominal=q_nom_in * density_in)), GasOut=dict(Q=dict(nominal=q_nom_out), mass_flow=dict(nominal=q_nom_out * density_out)), **self._get_cost_figure_modifiers(asset), + **self.get_owner(asset), ) + modifiers.update(self.get_carrier_id(asset)) return GasSubstation, modifiers diff --git a/src/mesido/esdl/esdl_mixin.py b/src/mesido/esdl/esdl_mixin.py index f52058192..91d378321 100644 --- a/src/mesido/esdl/esdl_mixin.py +++ b/src/mesido/esdl/esdl_mixin.py @@ -148,10 +148,15 @@ def __init__(self, *args, **kwargs) -> None: self._override_gas_pipe_classes = dict() self.override_gas_pipe_classes() + self.__connections = self.__model.get_connections() + self.name_to_esdl_id_map = dict() super().__init__(*args, **kwargs) + def get_connections(self): + return self.__connections + @property def esdl_bytes_string(self) -> bytes: """ diff --git a/src/mesido/esdl/esdl_model_base.py b/src/mesido/esdl/esdl_model_base.py index 7f2d58fb8..7080847d6 100644 --- a/src/mesido/esdl/esdl_model_base.py +++ b/src/mesido/esdl/esdl_model_base.py @@ -26,6 +26,7 @@ class _ESDLModelBase(_Model): primary_port_name_convention = "prim" secondary_port_name_convention = "sec" + __connections = None def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: str) -> None: """ @@ -283,6 +284,7 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: # therefore do the nodes first, and do all remaining connections # after. connections = set() + model_connections = [] for asset in [*node_assets, *bus_assets, *gas_node_assets]: component = getattr(self, asset.name) @@ -300,16 +302,25 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: continue if isinstance(port.carrier, esdl.HeatCommodity): self.connect(getattr(component, node_suf)[i], port_map[connected_to.id]) + model_connections.append([getattr(component, node_suf)[i].name, port_map[connected_to.id].name]) connections.add(conn) i += 1 elif isinstance(port.carrier, esdl.ElectricityCommodity): self.connect( - getattr(component, elec_node_suf)[i], port_map[connected_to.id] + getattr(component, elec_node_suf)[i].name, port_map[connected_to.id].name + ) + model_connections.append( + [ + getattr(component, elec_node_suf)[i].name, port_map[connected_to.id].name + ] ) connections.add(conn) i += 1 elif isinstance(port.carrier, esdl.GasCommodity): self.connect(getattr(component, gas_node_suf)[i], port_map[connected_to.id]) + model_connections.append( + [getattr(component, gas_node_suf)[i].name, port_map[connected_to.id].name] + ) connections.add(conn) i += 1 else: @@ -350,4 +361,10 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: continue self.connect(port_map[port.id], port_map[connected_to.id]) + model_connections.append([port_map[port.id].name, port_map[connected_to.id].name]) connections.add(conn) + + self.__connections = model_connections + + def get_connections(self): + return self.__connections diff --git a/src/mesido/esdl/esdl_parser.py b/src/mesido/esdl/esdl_parser.py index 1a1707ee9..d19234af4 100644 --- a/src/mesido/esdl/esdl_parser.py +++ b/src/mesido/esdl/esdl_parser.py @@ -56,7 +56,7 @@ def read_esdl(self) -> None: id=x.id, id_number_mapping=id_to_idnumber_map[x.id], temperature=temperature, - type="milp", + type="heat", ) elif isinstance(x, esdl.esdl.ElectricityCommodity): if x.id not in id_to_idnumber_map: diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 9cff93123..9806ca57f 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -77,6 +77,7 @@ def __init__(self, *args, **kwargs): # Variable for realized revenue self._asset_revenue_map = {} + self._asset_cost_map = {} self.__asset_revenue_var = {} self.__asset_revenue_nominals = {} self.__asset_revenue_bounds = {} @@ -95,6 +96,93 @@ def pre(self): parameters = self.parameters(0) bounds = self.bounds() + for owner, assets in self.energy_system_owners.items(): + # All assets already have individual costs that can be used for the business cases + # Here we create revenue variables for the locations where energy is consumed or exhanged between owners + + # Create revenue variable for every demand type asset + for asset in assets: + if asset in [*self.energy_system_components.get("heat_demand", []), + *self.energy_system_components.get("gas_demand", []), + *self.energy_system_components.get("electricity_demand", []), + ]: + var_name = f"{asset}__revenue" + self._asset_revenue_map[asset] = var_name + self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + nominal_power = None + for _id, attr in self.get_electricity_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") + if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + carrier_name = attr["name"] + for _id, attr in self.get_gas_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.Gas_demand") + if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + carrier_name = attr["name"] + for _id, attr in self.get_heat_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.Heat_demand") + if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + carrier_name = attr["name"] + self.__asset_revenue_nominals[var_name] = ( + max( + np.mean(self.get_timeseries(f"{carrier_name}.price_profile").values) + * nominal_power, + 1.0e2, + ) + if nominal_power is not None + else 1.0e2 + ) + self.__asset_revenue_bounds[var_name] = (0., np.inf) + + # Find the assets that exchange energy with other owners and create a revenue variable for them as well + for asset in assets: + conn = self.get_connections() + for connection in conn: + if asset == connection[0].split(".")[0] or asset == connection[1].split(".")[0]: + other_asset = connection[0].split(".")[0] if asset == connection[1].split(".")[0] else connection[1].split(".")[0] + port = connection[1].split(".")[-1] if asset == connection[1].split(".")[0] else connection[0].split(".")[-1] + if other_asset not in assets: + if "In" in port: + var_name = f"{asset}__cost" + self._asset_cost_map[asset] = var_name + else: + var_name = f"{asset}__revenue" + self._asset_revenue_map[asset] = var_name + self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + nominal_power = None + carrier_name = None + for _id, attr in self.get_electricity_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") + if attr["id_number_mapping"] == parameters[ + f"{asset}.id_mapping_carrier"]: + carrier_name = attr["name"] + for _id, attr in self.get_gas_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.Gas_demand") + if attr["id_number_mapping"] == parameters[ + f"{asset}.id_mapping_carrier"]: + carrier_name = attr["name"] + for _id, attr in self.get_heat_carriers().items(): + nominal_power = self.variable_nominal(f"{asset}.{port}.Heat") + if attr["id_number_mapping"] == parameters[ + f"{asset}.{port}.carrier_id"]: + carrier_name = attr["name"] + self.__asset_revenue_nominals[var_name] = ( + max( + np.mean( + self.get_timeseries(f"{carrier_name}.price_profile").values) + * nominal_power, + 1.0e2, + ) + if nominal_power is not None and carrier_name is not None + else 1.0e2 + ) + self.__asset_revenue_bounds[var_name] = (0., np.inf) + + + + + + + # Making the cost variables; fixed_operational_cost, variable_operational_cost, # installation_cost and investment_cost for asset_name in [ diff --git a/src/mesido/pycml/component_library/milp/_internal/heat_component.py b/src/mesido/pycml/component_library/milp/_internal/heat_component.py index ed8ef61b3..3e558a673 100644 --- a/src/mesido/pycml/component_library/milp/_internal/heat_component.py +++ b/src/mesido/pycml/component_library/milp/_internal/heat_component.py @@ -22,6 +22,8 @@ def __init__(self, name, **modifiers): super().__init__(name, **modifiers) self.state = 1 + self.owner = "__" + self.variable_operational_cost_coefficient = 0.0 self.fixed_operational_cost_coefficient = 0.0 self.investment_cost_coefficient = 0.0 diff --git a/src/mesido/pycml/component_library/milp/electricity/electricity_base.py b/src/mesido/pycml/component_library/milp/electricity/electricity_base.py index 993f15328..24bd881ba 100644 --- a/src/mesido/pycml/component_library/milp/electricity/electricity_base.py +++ b/src/mesido/pycml/component_library/milp/electricity/electricity_base.py @@ -12,6 +12,8 @@ class ElectricityPort(ElectricityComponent, Connector): def __init__(self, name, **modifiers): super().__init__(name, **modifiers) + self.carrier_id = -1 + self.add_variable(Variable, "Power") self.add_variable(Variable, "V", min=0.0) self.add_variable(Variable, "I") diff --git a/src/mesido/pycml/component_library/milp/gas/gas_base.py b/src/mesido/pycml/component_library/milp/gas/gas_base.py index 3f7f1ead3..6fd3cbc44 100644 --- a/src/mesido/pycml/component_library/milp/gas/gas_base.py +++ b/src/mesido/pycml/component_library/milp/gas/gas_base.py @@ -14,6 +14,9 @@ def __init__(self, name, **modifiers): super().__init__(name, **modifiers) # TODO: think of more elegant approach for Q_shadow, currently required to ensure that # every port has a unique variable to make the correct port mapping + + self.carrier_id = -1 + self.add_variable(Variable, "Q") # [m3/s] self.add_variable(Variable, "Q_shadow") self.add_variable(Variable, "mass_flow") # [g/s] diff --git a/src/mesido/pycml/component_library/milp/heat/heat_port.py b/src/mesido/pycml/component_library/milp/heat/heat_port.py index 9ba7ef285..f5a9c61f5 100644 --- a/src/mesido/pycml/component_library/milp/heat/heat_port.py +++ b/src/mesido/pycml/component_library/milp/heat/heat_port.py @@ -12,6 +12,8 @@ class HeatPort(HeatComponent, Connector): def __init__(self, name, **modifiers): super().__init__(name, **modifiers) + self.carrier_id = -1 + self.add_variable(Variable, "Heat") self.add_variable(Variable, "Q") self.add_variable(Variable, "H") diff --git a/src/mesido/pycml/component_library/milp/heat/node.py b/src/mesido/pycml/component_library/milp/heat/node.py index 1c767077f..087cb3546 100644 --- a/src/mesido/pycml/component_library/milp/heat/node.py +++ b/src/mesido/pycml/component_library/milp/heat/node.py @@ -20,6 +20,7 @@ def __init__(self, name, **modifiers): self.n = 2 assert self.n >= 2 + self.carrier_id = -5 self.add_variable(HeatPort, "HeatConn", self.n) self.add_variable(Variable, "H") @@ -31,3 +32,6 @@ def __init__(self, name, **modifiers): for i in range(1, self.n + 1): self.add_equation(self.HeatConn[i].H - self.H) # Q and Heat to be set in the mixin + + for i in range(1, self.n + 1): + self.HeatConn[i].carrier_id = self.carrier_id diff --git a/tests/models/heat_exchange/input/timeseries.csv b/tests/models/heat_exchange/input/timeseries.csv new file mode 100644 index 000000000..4008a68fd --- /dev/null +++ b/tests/models/heat_exchange/input/timeseries.csv @@ -0,0 +1,4 @@ +DateTime,HeatingDemand_3322,HeatingDemand_18aa +01-01-2019 00:00, 350000., 350000. +01-01-2019 01:00, 350000., 350000. +01-01-2019 02:00, 350000., 350000. diff --git a/tests/models/heat_exchange/model/network_with_parties.esdl b/tests/models/heat_exchange/model/network_with_parties.esdl new file mode 100644 index 000000000..7bf18466c --- /dev/null +++ b/tests/models/heat_exchange/model/network_with_parties.esdl @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/models/heat_exchange/src/run_heat_exchanger.py b/tests/models/heat_exchange/src/run_heat_exchanger.py index a1847bcac..fb9530694 100644 --- a/tests/models/heat_exchange/src/run_heat_exchanger.py +++ b/tests/models/heat_exchange/src/run_heat_exchanger.py @@ -315,9 +315,9 @@ def constraints(self, ensemble_member): if __name__ == "__main__": solution = run_optimization_problem( HeatProblem, - esdl_file_name="heat_exchanger.esdl", + esdl_file_name="network_with_parties.esdl", esdl_parser=ESDLFileParser, profile_reader=ProfileReaderFromFile, - input_timeseries_file="timeseries_import.xml", + input_timeseries_file="timeseries.csv", ) results = solution.extract_results() From 4d398848234d4120a8e991c651d9d87a5ed04ee9 Mon Sep 17 00:00:00 2001 From: Rojer Date: Wed, 10 Apr 2024 13:35:05 +0200 Subject: [PATCH 2/9] starting with the revenue constraints, electrolyser example now runs --- src/mesido/component_type_mixin.py | 4 +- src/mesido/electricity_physics_mixin.py | 2 +- src/mesido/esdl/esdl_heat_model.py | 115 ++++++++++----- src/mesido/esdl/esdl_model_base.py | 18 ++- src/mesido/financial_mixin.py | 138 +++++++++++------- .../milp/electricity/electricity_node.py | 4 + .../component_library/milp/gas/gas_node.py | 4 + .../pycml/component_library/milp/heat/node.py | 2 +- 8 files changed, 192 insertions(+), 95 deletions(-) diff --git a/src/mesido/component_type_mixin.py b/src/mesido/component_type_mixin.py index e64d54c3f..431fc08e1 100644 --- a/src/mesido/component_type_mixin.py +++ b/src/mesido/component_type_mixin.py @@ -315,7 +315,9 @@ def energy_system_components(self) -> Dict[str, Set[str]]: # Find the components in model, detection by string # (name.component_type: type) - component_types = sorted({v for k, v in string_parameters.items() if "component_type" in k}) + component_types = sorted( + {v for k, v in string_parameters.items() if "component_type" in k} + ) components = {} for c in component_types: diff --git a/src/mesido/electricity_physics_mixin.py b/src/mesido/electricity_physics_mixin.py index 4f405f1ac..ced352a79 100644 --- a/src/mesido/electricity_physics_mixin.py +++ b/src/mesido/electricity_physics_mixin.py @@ -449,7 +449,7 @@ def __electricity_storage_path_constraints(self, ensemble_member): current_in = self.state(f"{asset}.ElectricityIn.I") # is_charging is 1 if charging and powerin>0 - big_m = 2 * max(abs(self.bounds()[f"{asset}.ElectricityIn.Power"])) + big_m = 2 * max(abs(np.asarray(self.bounds()[f"{asset}.ElectricityIn.Power"]))) is_charging = self.state(f"{asset}__is_charging") constraints.append(((power_in + (1 - is_charging) * big_m) / power_nom, 0.0, np.inf)) constraints.append(((power_in - is_charging * big_m) / power_nom, -np.inf, 0.0)) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index e74f4aff0..e6681a3d2 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -93,6 +93,26 @@ def _rho_cp_modifiers(self) -> Dict: """ return dict(rho=self.rho, cp=self.cp) + def merge_modifiers(self, a: dict, b: dict): + """ + Recursive (not in place) merge of dictionaries. + + :param a: Base dictionary to merge. + :param b: Dictionary to merge on top of base dictionary. + :return: Merged dictionary + """ + b = b.copy() + + for k, v in a.items(): + if isinstance(v, dict): + b_node = b.setdefault(k, {}) + b[k] = self.merge_modifiers(v, b_node) + else: + if k not in b: + b[k] = v + + return b + def get_density(self, asset_name, carrier): # TODO: gas carrier temperature still needs to be resolved. # The default of 20°C is also used in the head_loss_class. Thus, when updating ensure it @@ -125,11 +145,22 @@ def get_density(self, asset_name, carrier): return density * 1.0e3 # to convert from kg/m3 to g/m3 def get_owner(self, asset): - return dict(owner=asset.attributes["isOwnedBy"].name if asset.attributes["isOwnedBy"] is not None else "NoOwner") + return dict( + owner=( + asset.attributes["isOwnedBy"].name + if asset.attributes["isOwnedBy"] is not None + else "NoOwner" + ) + ) def get_carrier_id(self, asset, node=False, n=0): if not node: - if asset.asset_type == "HeatExchange" or asset.asset_type == "HeatPump": + if ( + asset.in_ports is not None + and asset.out_ports is not None + and len(asset.in_ports) >= 2 + and len(asset.out_ports) >= 2 + ): ## heat pump and heat exchanger carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] if "Prim" in asset.in_ports[0].name: prim_in_id = carrier["id_number_mapping"] @@ -140,6 +171,11 @@ def get_carrier_id(self, asset, node=False, n=0): prim_in_id = carrier["id_number_mapping"] else: sec_in_id = carrier["id_number_mapping"] + if len(asset.in_ports) == 3: + if "Prim" in asset.in_ports[1].name: + prim_in_id = carrier["id_number_mapping"] + else: + sec_in_id = carrier["id_number_mapping"] carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] if "Prim" in asset.out_ports[0].name: prim_out_id = carrier["id_number_mapping"] @@ -151,15 +187,13 @@ def get_carrier_id(self, asset, node=False, n=0): else: sec_out_id = carrier["id_number_mapping"] ids = dict( - Primary=dict( - HeatIn=dict(carrier_id=prim_in_id), - HeatOut=dict(carrier_id=prim_out_id) - ), - Secondary=dict( - HeatIn=dict(carrier_id=sec_in_id), - HeatOut=dict(carrier_id=sec_out_id) - ), - ) + Primary=dict( + HeatIn=dict(carrier_id=prim_in_id), HeatOut=dict(carrier_id=prim_out_id) + ), + Secondary=dict( + HeatIn=dict(carrier_id=sec_in_id), HeatOut=dict(carrier_id=sec_out_id) + ), + ) return ids else: ids = dict() @@ -172,11 +206,17 @@ def get_carrier_id(self, asset, node=False, n=0): id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}Out" ids[port] = dict(carrier_id=id_number) - except KeyError: - carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}In" - ids[port] = dict(carrier_id=id_number) + except: + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}In" + ids[port] = dict(carrier_id=id_number) + except: + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}Out" + ids[port] = dict(carrier_id=id_number) return ids else: ids = dict() @@ -579,7 +619,7 @@ def convert_heat_pipe( **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return GasPipe, modifiers @@ -640,7 +680,7 @@ def convert_heat_pipe( if "T_ground" in asset.attributes.keys(): modifiers["T_ground"] = asset.attributes["T_ground"] - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return HeatPipe, modifiers @@ -684,7 +724,7 @@ def convert_pump(self, asset: Asset) -> Tuple[Type[Pump], MODIFIERS]: **self._rho_cp_modifiers, **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return Pump, modifiers @@ -816,7 +856,7 @@ def convert_heat_exchanger(self, asset: Asset) -> Tuple[Type[HeatExchanger], MOD **params, **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return HeatExchanger, modifiers def convert_heat_pump( @@ -900,7 +940,7 @@ def convert_heat_pump( **params, **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) if len(asset.in_ports) == 2: return HeatPump, modifiers elif len(asset.in_ports) == 3: @@ -975,7 +1015,7 @@ def convert_heat_source(self, asset: Asset) -> Tuple[Type[HeatSource], MODIFIERS **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) if asset.asset_type == "GeothermalSource": modifiers["nr_of_doublets"] = asset.attributes["aggregationCount"] @@ -1087,7 +1127,7 @@ def convert_ates(self, asset: Asset) -> Tuple[Type[ATES], MODIFIERS]: **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) # if no maxStorageTemperature is specified we assume a "regular" HT ATES model if ( @@ -1162,7 +1202,7 @@ def convert_control_valve(self, asset: Asset) -> Tuple[Type[ControlValve], MODIF **self._rho_cp_modifiers, **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return ControlValve, modifiers @@ -1196,7 +1236,7 @@ def convert_check_valve(self, asset: Asset) -> Tuple[Type[CheckValve], MODIFIERS **self._rho_cp_modifiers, **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return CheckValve, modifiers @@ -1238,7 +1278,7 @@ def convert_electricity_demand(self, asset: Asset) -> Tuple[Type[ElectricityDema **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return ElectricityDemand, modifiers @@ -1276,7 +1316,7 @@ def convert_electricity_source(self, asset: Asset) -> Tuple[Type[ElectricitySour **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) if asset.asset_type == "ElectricityProducer": return ElectricitySource, modifiers @@ -1326,7 +1366,7 @@ def convert_electricity_storage( **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return ElectricityStorage, modifiers @@ -1365,11 +1405,16 @@ def convert_electricity_node(self, asset: Asset) -> Tuple[Type[ElectricityNode], if isinstance(x, esdl.esdl.OutPort): sum_out += len(x.connectedTo) + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + except: + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + modifiers = dict( voltage_nominal=nominal_voltage, + carrier_id=carrier["id_number_mapping"], n=sum_in + sum_out, **self.get_owner(asset), - **self.get_carrier_id(asset, node=True, n=sum_in + sum_out) ) return ElectricityNode, modifiers @@ -1416,7 +1461,7 @@ def convert_electricity_cable(self, asset: Asset) -> Tuple[Type[ElectricityCable **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return ElectricityCable, modifiers def convert_gas_demand(self, asset: Asset) -> Tuple[Type[GasDemand], MODIFIERS]: @@ -1461,7 +1506,7 @@ def convert_gas_demand(self, asset: Asset) -> Tuple[Type[GasDemand], MODIFIERS]: **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return GasDemand, modifiers @@ -1502,7 +1547,7 @@ def convert_gas_source(self, asset: Asset) -> Tuple[Type[GasSource], MODIFIERS]: **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return GasSource, modifiers @@ -1571,7 +1616,7 @@ def equations(x): **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return Electrolyzer, modifiers @@ -1611,7 +1656,7 @@ def convert_gas_tank_storage(self, asset: Asset) -> Tuple[Type[GasTankStorage], **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return GasTankStorage, modifiers @@ -1644,7 +1689,7 @@ def convert_gas_substation(self, asset: Asset) -> Tuple[Type[GasSubstation], MOD **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) - modifiers.update(self.get_carrier_id(asset)) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return GasSubstation, modifiers diff --git a/src/mesido/esdl/esdl_model_base.py b/src/mesido/esdl/esdl_model_base.py index 7080847d6..041293315 100644 --- a/src/mesido/esdl/esdl_model_base.py +++ b/src/mesido/esdl/esdl_model_base.py @@ -302,16 +302,19 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: continue if isinstance(port.carrier, esdl.HeatCommodity): self.connect(getattr(component, node_suf)[i], port_map[connected_to.id]) - model_connections.append([getattr(component, node_suf)[i].name, port_map[connected_to.id].name]) + model_connections.append( + [getattr(component, node_suf)[i].name, port_map[connected_to.id].name] + ) connections.add(conn) i += 1 elif isinstance(port.carrier, esdl.ElectricityCommodity): self.connect( - getattr(component, elec_node_suf)[i].name, port_map[connected_to.id].name + getattr(component, elec_node_suf)[i], port_map[connected_to.id] ) model_connections.append( [ - getattr(component, elec_node_suf)[i].name, port_map[connected_to.id].name + getattr(component, elec_node_suf)[i].name, + port_map[connected_to.id].name, ] ) connections.add(conn) @@ -319,7 +322,10 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: elif isinstance(port.carrier, esdl.GasCommodity): self.connect(getattr(component, gas_node_suf)[i], port_map[connected_to.id]) model_connections.append( - [getattr(component, gas_node_suf)[i].name, port_map[connected_to.id].name] + [ + getattr(component, gas_node_suf)[i].name, + port_map[connected_to.id].name, + ] ) connections.add(conn) i += 1 @@ -361,7 +367,9 @@ def _esdl_convert(self, converter: _AssetToComponentBase, assets: Dict, prefix: continue self.connect(port_map[port.id], port_map[connected_to.id]) - model_connections.append([port_map[port.id].name, port_map[connected_to.id].name]) + model_connections.append( + [port_map[port.id].name, port_map[connected_to.id].name] + ) connections.add(conn) self.__connections = model_connections diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 9806ca57f..7dcf528d5 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -77,6 +77,7 @@ def __init__(self, *args, **kwargs): # Variable for realized revenue self._asset_revenue_map = {} + self._asset_revenue_variable_port_map = {} self._asset_cost_map = {} self.__asset_revenue_var = {} self.__asset_revenue_nominals = {} @@ -102,44 +103,58 @@ def pre(self): # Create revenue variable for every demand type asset for asset in assets: - if asset in [*self.energy_system_components.get("heat_demand", []), - *self.energy_system_components.get("gas_demand", []), - *self.energy_system_components.get("electricity_demand", []), - ]: + if asset in [ + *self.energy_system_components.get("heat_demand", []), + *self.energy_system_components.get("gas_demand", []), + *self.energy_system_components.get("electricity_demand", []), + ]: var_name = f"{asset}__revenue" self._asset_revenue_map[asset] = var_name self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) nominal_power = None for _id, attr in self.get_electricity_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: carrier_name = attr["name"] + nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") + self._asset_revenue_variable_port_map[var_name] = ( + f"{asset}.ElectricityIn.Power" + ) for _id, attr in self.get_gas_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.Gas_demand") if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: carrier_name = attr["name"] + nominal_power = self.variable_nominal(f"{asset}.Gas_demand") + self._asset_revenue_variable_port_map[var_name] = f"{asset}.GasIn.Q" for _id, attr in self.get_heat_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.Heat_demand") if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: carrier_name = attr["name"] + nominal_power = self.variable_nominal(f"{asset}.Heat_demand") + self._asset_revenue_variable_port_map[var_name] = f"{asset}.HeatIn.Heat" self.__asset_revenue_nominals[var_name] = ( - max( - np.mean(self.get_timeseries(f"{carrier_name}.price_profile").values) - * nominal_power, - 1.0e2, - ) - if nominal_power is not None - else 1.0e2 + max( + np.mean(self.get_timeseries(f"{carrier_name}.price_profile").values) + * nominal_power, + 1.0e2, ) - self.__asset_revenue_bounds[var_name] = (0., np.inf) + if nominal_power is not None + else 1.0e2 + ) + self.__asset_revenue_bounds[var_name] = (0.0, np.inf) # Find the assets that exchange energy with other owners and create a revenue variable for them as well for asset in assets: conn = self.get_connections() for connection in conn: if asset == connection[0].split(".")[0] or asset == connection[1].split(".")[0]: - other_asset = connection[0].split(".")[0] if asset == connection[1].split(".")[0] else connection[1].split(".")[0] - port = connection[1].split(".")[-1] if asset == connection[1].split(".")[0] else connection[0].split(".")[-1] + other_asset = ( + connection[0].split(".")[0] + if asset == connection[1].split(".")[0] + else connection[1].split(".")[0] + ) + port = ( + connection[1].split(".")[-1] + if asset == connection[1].split(".")[0] + else connection[0].split(".")[-1] + ) if other_asset not in assets: if "In" in port: var_name = f"{asset}__cost" @@ -151,37 +166,47 @@ def pre(self): nominal_power = None carrier_name = None for _id, attr in self.get_electricity_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") - if attr["id_number_mapping"] == parameters[ - f"{asset}.id_mapping_carrier"]: + self._asset_revenue_variable_port_map[var_name] = ( + f"{asset}.{port}.Power" + ) + if ( + attr["id_number_mapping"] + == parameters[f"{asset}.{port}.carrier_id"] + ): + nominal_power = self.variable_nominal(f"{asset}.{port}.Power") carrier_name = attr["name"] for _id, attr in self.get_gas_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.Gas_demand") - if attr["id_number_mapping"] == parameters[ - f"{asset}.id_mapping_carrier"]: + self._asset_revenue_variable_port_map[var_name] = ( + f"{asset}.{port}.Q" + ) + if ( + attr["id_number_mapping"] + == parameters[f"{asset}.{port}.carrier_id"] + ): + nominal_power = self.variable_nominal(f"{asset}.{port}.Q") carrier_name = attr["name"] for _id, attr in self.get_heat_carriers().items(): - nominal_power = self.variable_nominal(f"{asset}.{port}.Heat") - if attr["id_number_mapping"] == parameters[ - f"{asset}.{port}.carrier_id"]: + self._asset_revenue_variable_port_map[var_name] = ( + f"{asset}.{port}.Heat" + ) + if ( + attr["id_number_mapping"] + == parameters[f"{asset}.{port}.carrier_id"] + ): + nominal_power = self.variable_nominal(f"{asset}.{port}.Heat") carrier_name = attr["name"] self.__asset_revenue_nominals[var_name] = ( max( np.mean( - self.get_timeseries(f"{carrier_name}.price_profile").values) + self.get_timeseries(f"{carrier_name}.price_profile").values + ) * nominal_power, 1.0e2, ) - if nominal_power is not None and carrier_name is not None + if nominal_power is not None else 1.0e2 ) - self.__asset_revenue_bounds[var_name] = (0., np.inf) - - - - - - + self.__asset_revenue_bounds[var_name] = (0.0, np.inf) # Making the cost variables; fixed_operational_cost, variable_operational_cost, # installation_cost and investment_cost @@ -1328,32 +1353,41 @@ def __revenue_constraints(self, ensemble_member): # TODO: add fixed price default from ESDL in case no price profile is defined. parameters = self.parameters(ensemble_member) - for demand in [ - *self.energy_system_components.get("gas_demand", []), - *self.energy_system_components.get("electricity_demand", []), - ]: + for asset, variable_revenue_var in self._asset_revenue_map.items(): carrier_name = None + port = None for _id, attr in self.get_electricity_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{demand}.id_mapping_carrier"]: + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" + ] + ): carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" for _id, attr in self.get_gas_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{demand}.id_mapping_carrier"]: + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" + ] + ): + carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" + for _id, attr in self.get_heat_carriers().items(): + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" + ] + ): carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" if carrier_name is not None: price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values + energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) - if demand in self.energy_system_components.get("gas_demand", []): - energy_flow = self.__state_vector_scaled( - f"{demand}.Gas_demand_mass_flow", ensemble_member # g/s - ) - - elif demand in self.energy_system_components.get("electricity_demand", []): - energy_flow = self.__state_vector_scaled( - f"{demand}.Electricity_demand", ensemble_member - ) - - variable_revenue_var = self._asset_revenue_map[demand] variable_revenue = self.extra_variable(variable_revenue_var, ensemble_member) nominal = self.variable_nominal(variable_revenue_var) diff --git a/src/mesido/pycml/component_library/milp/electricity/electricity_node.py b/src/mesido/pycml/component_library/milp/electricity/electricity_node.py index 2aeabf80c..17ec8bc25 100644 --- a/src/mesido/pycml/component_library/milp/electricity/electricity_node.py +++ b/src/mesido/pycml/component_library/milp/electricity/electricity_node.py @@ -24,6 +24,7 @@ def __init__(self, name, **modifiers): self.n = 2 assert self.n >= 2 + self.carrier_id = -1 self.add_variable(ElectricityPort, "ElectricityConn", self.n) self.add_variable(Variable, "V", min=0.0, nominal=self.voltage_nominal) @@ -33,3 +34,6 @@ def __init__(self, name, **modifiers): # Because the orientation of the connected cables are important to setup the energy # conservation, these constraints are added in the mixin. + + for i in range(1, self.n + 1): + self.ElectricityConn[i].carrier_id = self.carrier_id diff --git a/src/mesido/pycml/component_library/milp/gas/gas_node.py b/src/mesido/pycml/component_library/milp/gas/gas_node.py index b68246301..9a8e31e1a 100644 --- a/src/mesido/pycml/component_library/milp/gas/gas_node.py +++ b/src/mesido/pycml/component_library/milp/gas/gas_node.py @@ -20,6 +20,7 @@ def __init__(self, name, **modifiers): self.n = 2 assert self.n >= 2 + self.carrier_id = -1 self.add_variable(GasPort, "GasConn", self.n) self.add_variable(Variable, "H", min=0.0) @@ -29,3 +30,6 @@ def __init__(self, name, **modifiers): # Because the orientation of the connected pipes are important to setup the mass # conservation, these constraints are added in the mixin. + + for i in range(1, self.n + 1): + self.GasConn[i].carrier_id = self.carrier_id diff --git a/src/mesido/pycml/component_library/milp/heat/node.py b/src/mesido/pycml/component_library/milp/heat/node.py index 087cb3546..1b098908d 100644 --- a/src/mesido/pycml/component_library/milp/heat/node.py +++ b/src/mesido/pycml/component_library/milp/heat/node.py @@ -20,7 +20,7 @@ def __init__(self, name, **modifiers): self.n = 2 assert self.n >= 2 - self.carrier_id = -5 + self.carrier_id = -1 self.add_variable(HeatPort, "HeatConn", self.n) self.add_variable(Variable, "H") From 9631f6b61a5cd10cf65849ef4374fabadf79f8f0 Mon Sep 17 00:00:00 2001 From: Rojer Date: Thu, 11 Apr 2024 16:23:04 +0200 Subject: [PATCH 3/9] things seem to run as expected now and revenues are computed, however we need to give it the correct sign --- src/mesido/esdl/esdl_heat_model.py | 3 + src/mesido/financial_mixin.py | 205 ++++++++---------- .../models/heat_exchange/input/timeseries.csv | 8 +- .../heat_exchange/src/run_heat_exchanger.py | 7 +- 4 files changed, 105 insertions(+), 118 deletions(-) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index e6681a3d2..8579465d1 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -422,6 +422,7 @@ def convert_heat_buffer(self, asset: Asset) -> Tuple[Type[HeatBuffer], MODIFIERS **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return HeatBuffer, modifiers @@ -461,6 +462,7 @@ def convert_heat_demand(self, asset: Asset) -> Tuple[Type[HeatDemand], MODIFIERS **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return HeatDemand, modifiers @@ -497,6 +499,7 @@ def convert_cold_demand(self, asset: Asset) -> Tuple[Type[ColdDemand], MODIFIERS **self._get_cost_figure_modifiers(asset), **self.get_owner(asset), ) + modifiers = self.merge_modifiers(modifiers, self.get_carrier_id(asset)) return ColdDemand, modifiers diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 7dcf528d5..e594e95ab 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -78,7 +78,6 @@ def __init__(self, *args, **kwargs): # Variable for realized revenue self._asset_revenue_map = {} self._asset_revenue_variable_port_map = {} - self._asset_cost_map = {} self.__asset_revenue_var = {} self.__asset_revenue_nominals = {} self.__asset_revenue_bounds = {} @@ -108,37 +107,42 @@ def pre(self): *self.energy_system_components.get("gas_demand", []), *self.energy_system_components.get("electricity_demand", []), ]: - var_name = f"{asset}__revenue" - self._asset_revenue_map[asset] = var_name - self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + var_name = None + nominal_power = None + carrier_name = None for _id, attr in self.get_electricity_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + if attr["id_number_mapping"] == parameters[f"{asset}.ElectricityIn.carrier_id"]: + var_name = f"{asset}__revenue_ElectricityIn" carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") self._asset_revenue_variable_port_map[var_name] = ( f"{asset}.ElectricityIn.Power" ) for _id, attr in self.get_gas_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + if attr["id_number_mapping"] == parameters[f"{asset}.GasIn.carrier_id"]: + var_name = f"{asset}__revenue_GasIn" carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Gas_demand") self._asset_revenue_variable_port_map[var_name] = f"{asset}.GasIn.Q" for _id, attr in self.get_heat_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{asset}.id_mapping_carrier"]: + if attr["id_number_mapping"] == parameters[f"{asset}.HeatIn.carrier_id"]: + var_name = f"{asset}__revenue_HeatIn" carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Heat_demand") self._asset_revenue_variable_port_map[var_name] = f"{asset}.HeatIn.Heat" - self.__asset_revenue_nominals[var_name] = ( - max( - np.mean(self.get_timeseries(f"{carrier_name}.price_profile").values) - * nominal_power, - 1.0e2, + if carrier_name is not None: + self._asset_revenue_map[asset] = [var_name] + self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + try: + price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values + except KeyError: + price_profile = np.ones(len(self.times())) + self.__asset_revenue_nominals[var_name] = ( + np.mean(price_profile) + * nominal_power ) - if nominal_power is not None - else 1.0e2 - ) - self.__asset_revenue_bounds[var_name] = (0.0, np.inf) + self.__asset_revenue_bounds[var_name] = (0.0, np.inf) # Find the assets that exchange energy with other owners and create a revenue variable for them as well for asset in assets: @@ -150,19 +154,16 @@ def pre(self): if asset == connection[1].split(".")[0] else connection[1].split(".")[0] ) - port = ( - connection[1].split(".")[-1] + temp = ( + connection[1].split(".")[1:] if asset == connection[1].split(".")[0] - else connection[0].split(".")[-1] + else connection[0].split(".")[1:] ) + port = temp[0] + for x in temp[1:]: + port = port + "." + x if other_asset not in assets: - if "In" in port: - var_name = f"{asset}__cost" - self._asset_cost_map[asset] = var_name - else: - var_name = f"{asset}__revenue" - self._asset_revenue_map[asset] = var_name - self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + var_name = f"{asset}__revenue_{port}" nominal_power = None carrier_name = None for _id, attr in self.get_electricity_carriers().items(): @@ -195,18 +196,22 @@ def pre(self): ): nominal_power = self.variable_nominal(f"{asset}.{port}.Heat") carrier_name = attr["name"] - self.__asset_revenue_nominals[var_name] = ( - max( - np.mean( - self.get_timeseries(f"{carrier_name}.price_profile").values - ) - * nominal_power, - 1.0e2, + if carrier_name is not None: + try: + self._asset_revenue_map[asset].append(var_name) + except KeyError: + self._asset_revenue_map[asset] = [var_name] + self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) + try: + price_profile = self.get_timeseries( + f"{carrier_name}.price_profile").values + except NameError or KeyError: + price_profile = np.ones(len(self.times())) + self.__asset_revenue_nominals[var_name] = ( + np.mean(price_profile) + * nominal_power if nominal_power is not None else 1.e2 ) - if nominal_power is not None - else 1.0e2 - ) - self.__asset_revenue_bounds[var_name] = (0.0, np.inf) + self.__asset_revenue_bounds[var_name] = (-np.inf, np.inf) # Making the cost variables; fixed_operational_cost, variable_operational_cost, # installation_cost and investment_cost @@ -435,37 +440,6 @@ def pre(self): else 1.0e2 ) - # Realized revenue - if (asset_name) in [ - *self.energy_system_components.get("electricity_demand", []), - *self.energy_system_components.get("gas_demand", []), - ]: - - carrier_name = None - for _id, attr in self.get_electricity_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{asset_name}.id_mapping_carrier"]: - carrier_name = attr["name"] - for _id, attr in self.get_gas_carriers().items(): - if attr["id_number_mapping"] == parameters[f"{asset_name}.id_mapping_carrier"]: - carrier_name = attr["name"] - if carrier_name is not None: - asset_revenue_var = f"{asset_name}__revenue" - self._asset_revenue_map[asset_name] = asset_revenue_var - self.__asset_revenue_var[asset_revenue_var] = ca.MX.sym(asset_revenue_var) - self.__asset_revenue_bounds[asset_revenue_var] = ( - 0.0, - np.inf, - ) - self.__asset_revenue_nominals[asset_revenue_var] = ( - max( - np.mean(self.get_timeseries(f"{carrier_name}.price_profile").values) - * nominal_fixed_operational, - 1.0e2, - ) - if nominal_fixed_operational is not None - else 1.0e2 - ) - for asset in [ *self.energy_system_components.get("heat_source", []), *self.energy_system_components.get("heat_demand", []), @@ -1353,50 +1327,57 @@ def __revenue_constraints(self, ensemble_member): # TODO: add fixed price default from ESDL in case no price profile is defined. parameters = self.parameters(ensemble_member) - for asset, variable_revenue_var in self._asset_revenue_map.items(): - - carrier_name = None - port = None - for _id, attr in self.get_electricity_carriers().items(): - if ( - attr["id_number_mapping"] - == parameters[ - f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" - ] - ): - carrier_name = attr["name"] - port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" - for _id, attr in self.get_gas_carriers().items(): - if ( - attr["id_number_mapping"] - == parameters[ - f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" - ] - ): - carrier_name = attr["name"] - port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" - for _id, attr in self.get_heat_carriers().items(): - if ( - attr["id_number_mapping"] - == parameters[ - f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" - ] - ): - carrier_name = attr["name"] - port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" - if carrier_name is not None: - price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values - energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) - - variable_revenue = self.extra_variable(variable_revenue_var, ensemble_member) - nominal = self.variable_nominal(variable_revenue_var) - - sum = 0.0 - timesteps = np.diff(self.times()) / 3600.0 - for i in range(1, len(self.times())): - sum += price_profile[i] * energy_flow[i] * timesteps[i - 1] - - constraints.append(((variable_revenue - sum) / (nominal), 0.0, 0.0)) + for asset, variable_revenue_vars in self._asset_revenue_map.items(): + for variable_revenue_var in variable_revenue_vars: + carrier_name = None + port = None + for _id, attr in self.get_electricity_carriers().items(): + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" + ] + ): + carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" + for _id, attr in self.get_gas_carriers().items(): + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" + ] + ): + carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" + for _id, attr in self.get_heat_carriers().items(): + temp = self._asset_revenue_variable_port_map[variable_revenue_var].split('.') + full_port = temp[1] + for x in temp[2:-1]: + full_port = full_port + "." + x + if ( + attr["id_number_mapping"] + == parameters[ + f"{asset}.{full_port}.carrier_id" + ] + ): + carrier_name = attr["name"] + port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" + if carrier_name is not None: + try: + price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values + except KeyError: + price_profile = np.ones(len(self.times())) + energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) + + variable_revenue = self.extra_variable(variable_revenue_var, ensemble_member) + nominal = self.variable_nominal(variable_revenue_var) + + sum = 0.0 + timesteps = np.diff(self.times()) / 3600.0 + for i in range(1, len(self.times())): + sum += price_profile[i] * energy_flow[i] * timesteps[i - 1] + + constraints.append(((variable_revenue - sum) / (nominal), 0.0, 0.0)) return constraints diff --git a/tests/models/heat_exchange/input/timeseries.csv b/tests/models/heat_exchange/input/timeseries.csv index 4008a68fd..fd7ed1290 100644 --- a/tests/models/heat_exchange/input/timeseries.csv +++ b/tests/models/heat_exchange/input/timeseries.csv @@ -1,4 +1,4 @@ -DateTime,HeatingDemand_3322,HeatingDemand_18aa -01-01-2019 00:00, 350000., 350000. -01-01-2019 01:00, 350000., 350000. -01-01-2019 02:00, 350000., 350000. +DateTime,HeatingDemand_3322,HeatingDemand_18aa,heat1,heat1_ret,heat2,heat2_ret +01-01-2019 00:00, 350000., 350000.,1.,1.,2.,2. +01-01-2019 01:00, 350000., 350000.,1.,1.,2.,2. +01-01-2019 02:00, 350000., 350000.,1.,1.,2.,2. diff --git a/tests/models/heat_exchange/src/run_heat_exchanger.py b/tests/models/heat_exchange/src/run_heat_exchanger.py index fb9530694..d7f22afde 100644 --- a/tests/models/heat_exchange/src/run_heat_exchanger.py +++ b/tests/models/heat_exchange/src/run_heat_exchanger.py @@ -1,8 +1,10 @@ +from mesido.esdl.esdl_additional_vars_mixin import ESDLAdditionalVarsMixin from mesido.esdl.esdl_mixin import ESDLMixin from mesido.esdl.esdl_parser import ESDLFileParser from mesido.esdl.profile_parser import ProfileReaderFromFile from mesido.head_loss_class import HeadLossOption from mesido.physics_mixin import PhysicsMixin +from mesido.techno_economic_mixin import TechnoEconomicMixin from mesido.workflows.io.write_output import ScenarioOutput import numpy as np @@ -70,7 +72,8 @@ def function(self, optimization_problem, ensemble_member): class HeatProblem( ScenarioOutput, _GoalsAndOptions, - PhysicsMixin, + ESDLAdditionalVarsMixin, + TechnoEconomicMixin, LinearizedOrderGoalProgrammingMixin, GoalProgrammingMixin, ESDLMixin, @@ -208,7 +211,7 @@ def constraints(self, ensemble_member): class HeatProblemTvarDisableHEX( _GoalsAndOptions, - PhysicsMixin, + TechnoEconomicMixin, LinearizedOrderGoalProgrammingMixin, GoalProgrammingMixin, ESDLMixin, From c91d89e738bbdb251c8d87bb8b5cc98a2541b8f1 Mon Sep 17 00:00:00 2001 From: Rojer Date: Thu, 11 Apr 2024 16:46:35 +0200 Subject: [PATCH 4/9] signs are now fixed, everything seems to be consistent now, we should now add variables and constriants for computing the profit on owner level --- src/mesido/financial_mixin.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index e594e95ab..a9424ba78 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -4,6 +4,7 @@ import casadi as ca from mesido.base_component_type_mixin import BaseComponentTypeMixin +from mesido.heat_network_common import NodeConnectionDirection import numpy as np @@ -1368,7 +1369,26 @@ def __revenue_constraints(self, ensemble_member): except KeyError: price_profile = np.ones(len(self.times())) energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) - + energy_flow_sign = 1. + if "In" in port and asset not in [*self.energy_system_components.get("heat_demand", []), + *self.energy_system_components.get("gas_demand", []), + *self.energy_system_components.get("electricity_demand", [])]: + energy_flow_sign = -1. + if asset in self.energy_system_topology.nodes.keys(): + port_number = int([x for x in port if x.isdigit()][-1]) + if self.energy_system_topology.nodes[f"{asset}"][port_number][ + 1] == NodeConnectionDirection.IN: + energy_flow_sign = -1. + if asset in self.energy_system_topology.gas_nodes.keys(): + port_number = int([x for x in port if x.isdigit()][-1]) + if self.energy_system_topology.gas_nodes[f"{asset}"][port_number][ + 1] == NodeConnectionDirection.IN: + energy_flow_sign = -1. + if asset in self.energy_system_topology.busses.keys(): + port_number = int([x for x in port if x.isdigit()][-1]) + if self.energy_system_topology.busses[f"{asset}"][port_number][ + 1] == NodeConnectionDirection.IN: + energy_flow_sign = -1. variable_revenue = self.extra_variable(variable_revenue_var, ensemble_member) nominal = self.variable_nominal(variable_revenue_var) @@ -1377,7 +1397,7 @@ def __revenue_constraints(self, ensemble_member): for i in range(1, len(self.times())): sum += price_profile[i] * energy_flow[i] * timesteps[i - 1] - constraints.append(((variable_revenue - sum) / (nominal), 0.0, 0.0)) + constraints.append(((variable_revenue - sum * energy_flow_sign) / (nominal), 0.0, 0.0)) return constraints From 017e1c50b3e0f9bef688135196a8b7fe55043ebf Mon Sep 17 00:00:00 2001 From: Rojer Date: Thu, 18 Apr 2024 16:33:07 +0200 Subject: [PATCH 5/9] comments in financial mixin --- src/mesido/financial_mixin.py | 40 ++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index a9424ba78..416bfd104 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -102,21 +102,31 @@ def pre(self): # Here we create revenue variables for the locations where energy is consumed or exhanged between owners # Create revenue variable for every demand type asset + # The assumption is that if an organizational asset own the demand asset it has the + # single right to sell energy to that demand. for asset in assets: if asset in [ *self.energy_system_components.get("heat_demand", []), *self.energy_system_components.get("gas_demand", []), *self.energy_system_components.get("electricity_demand", []), ]: + # We check if we can find the carrier information used for that demand + # Note that we thus assume that the get_{commodity}_carriers() functions are set. + # In most practical cases this means using the ESDLExtraVarsMixin var_name = None - nominal_power = None carrier_name = None for _id, attr in self.get_electricity_carriers().items(): + # check if we can find the carried id if attr["id_number_mapping"] == parameters[f"{asset}.ElectricityIn.carrier_id"]: + # Note that we are including the port name at the end as we can have multiple revenue + # variables for a single asset as there can be multiple connections with different + # organizations over the different ports. var_name = f"{asset}__revenue_ElectricityIn" carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") + # Here we same the variable which should be used for the energy flow between + # the organizations and thus for computing the revenue. self._asset_revenue_variable_port_map[var_name] = ( f"{asset}.ElectricityIn.Power" ) @@ -132,6 +142,8 @@ def pre(self): carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Heat_demand") self._asset_revenue_variable_port_map[var_name] = f"{asset}.HeatIn.Heat" + # Only if we could fing the carrier information we create the revenue variable + # and thus the constraints. if carrier_name is not None: self._asset_revenue_map[asset] = [var_name] self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) @@ -146,15 +158,22 @@ def pre(self): self.__asset_revenue_bounds[var_name] = (0.0, np.inf) # Find the assets that exchange energy with other owners and create a revenue variable for them as well + # We loop over the assets for asset in assets: + # This is something extra I created. It is a nested list in the form of: + # [[asset1.HeatIn, asset2.HeatOut], [asset3.HeatIn, asset4.HeatOut], ...] conn = self.get_connections() + # We loop over the connections (yes this is inefficient) for connection in conn: + # Here we check if our asset is in the connection hence we find a connected asset if asset == connection[0].split(".")[0] or asset == connection[1].split(".")[0]: other_asset = ( connection[0].split(".")[0] if asset == connection[1].split(".")[0] else connection[1].split(".")[0] ) + # This bit of code is used to go from for example: + # HeatExchanger.Secondary.HeatIn -> Secondary.HeatIn temp = ( connection[1].split(".")[1:] if asset == connection[1].split(".")[0] @@ -163,6 +182,11 @@ def pre(self): port = temp[0] for x in temp[1:]: port = port + "." + x + + # Here we check that the connected asset is not also owned by the same entity. + # If the other asset belongs to an organization we create a revenue variable + # to represent the cashflow associated with the energy exchanged between the two + # organizations. if other_asset not in assets: var_name = f"{asset}__revenue_{port}" nominal_power = None @@ -198,6 +222,9 @@ def pre(self): nominal_power = self.variable_nominal(f"{asset}.{port}.Heat") carrier_name = attr["name"] if carrier_name is not None: + # Please note that we create a dict mapping with a list. As a single + # asset can have multiple revenue variables as it can have connections with + # other organizations on different ports. try: self._asset_revenue_map[asset].append(var_name) except KeyError: @@ -1328,10 +1355,13 @@ def __revenue_constraints(self, ensemble_member): # TODO: add fixed price default from ESDL in case no price profile is defined. parameters = self.parameters(ensemble_member) + # Here we compute the revenue by looping over all the revenue variables. for asset, variable_revenue_vars in self._asset_revenue_map.items(): + # note we need a nested loop as a asset can have multiple revenue variables for variable_revenue_var in variable_revenue_vars: carrier_name = None port = None + # We find the associated carrier and port (is the full string with asset name included) for _id, attr in self.get_electricity_carriers().items(): if ( attr["id_number_mapping"] @@ -1363,17 +1393,25 @@ def __revenue_constraints(self, ensemble_member): ): carrier_name = attr["name"] port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" + + # This if statement should not be needed... if carrier_name is not None: try: price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values except KeyError: price_profile = np.ones(len(self.times())) energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) + # Here we find the sign of the flow, meaning that energy going into the asset is + # negative revenue. energy_flow_sign = 1. if "In" in port and asset not in [*self.energy_system_components.get("heat_demand", []), *self.energy_system_components.get("gas_demand", []), *self.energy_system_components.get("electricity_demand", [])]: energy_flow_sign = -1. + # For nodes/busses we do this by checking the type of port in the topology object + # It is a bit of a hassle as we need to find the index of the port of the node + # for that we find the last digit (which by definition is the port index) in the + # port name string. if asset in self.energy_system_topology.nodes.keys(): port_number = int([x for x in port if x.isdigit()][-1]) if self.energy_system_topology.nodes[f"{asset}"][port_number][ From f253ca79eaa5f464cd0ea8b1bf01c2f6026ad90d Mon Sep 17 00:00:00 2001 From: Rojer Date: Mon, 22 Apr 2024 14:21:18 +0200 Subject: [PATCH 6/9] fixes for tests --- src/mesido/component_type_mixin.py | 6 +- src/mesido/financial_mixin.py | 75 +++++++++++-------- tests/models/heatpump/src/run_heat_pump.py | 3 + .../electrolyzer/input/timeseries.csv | 2 +- .../electrolyzer/model/h2.esdl | 2 +- tests/test_electrolyzer.py | 2 +- 6 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/mesido/component_type_mixin.py b/src/mesido/component_type_mixin.py index 431fc08e1..2be1215eb 100644 --- a/src/mesido/component_type_mixin.py +++ b/src/mesido/component_type_mixin.py @@ -316,7 +316,11 @@ def energy_system_components(self) -> Dict[str, Set[str]]: # Find the components in model, detection by string # (name.component_type: type) component_types = sorted( - {v for k, v in string_parameters.items() if "component_type" in k} + { + v + for k, v in string_parameters.items() + if ("component_type" in k or "component_subtype" in k) + } ) components = {} diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 416bfd104..4436ff151 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -118,7 +118,10 @@ def pre(self): carrier_name = None for _id, attr in self.get_electricity_carriers().items(): # check if we can find the carried id - if attr["id_number_mapping"] == parameters[f"{asset}.ElectricityIn.carrier_id"]: + if ( + attr["id_number_mapping"] + == parameters[f"{asset}.ElectricityIn.carrier_id"] + ): # Note that we are including the port name at the end as we can have multiple revenue # variables for a single asset as there can be multiple connections with different # organizations over the different ports. @@ -148,13 +151,13 @@ def pre(self): self._asset_revenue_map[asset] = [var_name] self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) try: - price_profile = self.get_timeseries(f"{carrier_name}.price_profile").values + price_profile = self.get_timeseries( + f"{carrier_name}.price_profile" + ).values except KeyError: price_profile = np.ones(len(self.times())) - self.__asset_revenue_nominals[var_name] = ( - np.mean(price_profile) - * nominal_power - ) + avg_price = np.mean(price_profile) if np.mean(price_profile) else 1.0 + self.__asset_revenue_nominals[var_name] = avg_price * nominal_power self.__asset_revenue_bounds[var_name] = (0.0, np.inf) # Find the assets that exchange energy with other owners and create a revenue variable for them as well @@ -232,12 +235,17 @@ def pre(self): self.__asset_revenue_var[var_name] = ca.MX.sym(var_name) try: price_profile = self.get_timeseries( - f"{carrier_name}.price_profile").values + f"{carrier_name}.price_profile" + ).values except NameError or KeyError: price_profile = np.ones(len(self.times())) + avg_price = ( + np.mean(price_profile) if np.mean(price_profile) else 1.0 + ) self.__asset_revenue_nominals[var_name] = ( - np.mean(price_profile) - * nominal_power if nominal_power is not None else 1.e2 + avg_price * nominal_power + if nominal_power is not None + else 1.0e2 ) self.__asset_revenue_bounds[var_name] = (-np.inf, np.inf) @@ -1381,16 +1389,11 @@ def __revenue_constraints(self, ensemble_member): carrier_name = attr["name"] port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" for _id, attr in self.get_heat_carriers().items(): - temp = self._asset_revenue_variable_port_map[variable_revenue_var].split('.') + temp = self._asset_revenue_variable_port_map[variable_revenue_var].split(".") full_port = temp[1] for x in temp[2:-1]: full_port = full_port + "." + x - if ( - attr["id_number_mapping"] - == parameters[ - f"{asset}.{full_port}.carrier_id" - ] - ): + if attr["id_number_mapping"] == parameters[f"{asset}.{full_port}.carrier_id"]: carrier_name = attr["name"] port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" @@ -1403,30 +1406,38 @@ def __revenue_constraints(self, ensemble_member): energy_flow = self.__state_vector_scaled(f"{port}", ensemble_member) # Here we find the sign of the flow, meaning that energy going into the asset is # negative revenue. - energy_flow_sign = 1. - if "In" in port and asset not in [*self.energy_system_components.get("heat_demand", []), - *self.energy_system_components.get("gas_demand", []), - *self.energy_system_components.get("electricity_demand", [])]: - energy_flow_sign = -1. + energy_flow_sign = 1.0 + if "In" in port and asset not in [ + *self.energy_system_components.get("heat_demand", []), + *self.energy_system_components.get("gas_demand", []), + *self.energy_system_components.get("electricity_demand", []), + ]: + energy_flow_sign = -1.0 # For nodes/busses we do this by checking the type of port in the topology object # It is a bit of a hassle as we need to find the index of the port of the node # for that we find the last digit (which by definition is the port index) in the # port name string. if asset in self.energy_system_topology.nodes.keys(): port_number = int([x for x in port if x.isdigit()][-1]) - if self.energy_system_topology.nodes[f"{asset}"][port_number][ - 1] == NodeConnectionDirection.IN: - energy_flow_sign = -1. + if ( + self.energy_system_topology.nodes[f"{asset}"][port_number][1] + == NodeConnectionDirection.IN + ): + energy_flow_sign = -1.0 if asset in self.energy_system_topology.gas_nodes.keys(): port_number = int([x for x in port if x.isdigit()][-1]) - if self.energy_system_topology.gas_nodes[f"{asset}"][port_number][ - 1] == NodeConnectionDirection.IN: - energy_flow_sign = -1. + if ( + self.energy_system_topology.gas_nodes[f"{asset}"][port_number][1] + == NodeConnectionDirection.IN + ): + energy_flow_sign = -1.0 if asset in self.energy_system_topology.busses.keys(): port_number = int([x for x in port if x.isdigit()][-1]) - if self.energy_system_topology.busses[f"{asset}"][port_number][ - 1] == NodeConnectionDirection.IN: - energy_flow_sign = -1. + if ( + self.energy_system_topology.busses[f"{asset}"][port_number][1] + == NodeConnectionDirection.IN + ): + energy_flow_sign = -1.0 variable_revenue = self.extra_variable(variable_revenue_var, ensemble_member) nominal = self.variable_nominal(variable_revenue_var) @@ -1435,7 +1446,9 @@ def __revenue_constraints(self, ensemble_member): for i in range(1, len(self.times())): sum += price_profile[i] * energy_flow[i] * timesteps[i - 1] - constraints.append(((variable_revenue - sum * energy_flow_sign) / (nominal), 0.0, 0.0)) + constraints.append( + ((variable_revenue - sum * energy_flow_sign) / (nominal), 0.0, 0.0) + ) return constraints diff --git a/tests/models/heatpump/src/run_heat_pump.py b/tests/models/heatpump/src/run_heat_pump.py index 43d4b20ca..93b4d68a0 100644 --- a/tests/models/heatpump/src/run_heat_pump.py +++ b/tests/models/heatpump/src/run_heat_pump.py @@ -127,6 +127,9 @@ def solver_options(self): highs_options["mip_rel_gap"] = 0.01 return options + def times(self, variable=None) -> np.ndarray: + return super().times(variable=variable)[:5] + def temperature_carriers(self): return self.esdl_carriers # geeft terug de carriers met multiple temperature options diff --git a/tests/models/unit_cases_electricity/electrolyzer/input/timeseries.csv b/tests/models/unit_cases_electricity/electrolyzer/input/timeseries.csv index 3565170c7..7ae8dad9a 100644 --- a/tests/models/unit_cases_electricity/electrolyzer/input/timeseries.csv +++ b/tests/models/unit_cases_electricity/electrolyzer/input/timeseries.csv @@ -1,4 +1,4 @@ -DateTime,elec,gas,WindPark_7f14 +DateTime,elec,Hydrogen,WindPark_7f14 01-01-2019 00:00, 1.0, 1000000.0, 100000000.0 01-01-2019 01:00, 1.0, 1000000.0, 100000000.0 01-01-2019 02:00, 1.0, 1000000.0, 100000000.0 diff --git a/tests/models/unit_cases_electricity/electrolyzer/model/h2.esdl b/tests/models/unit_cases_electricity/electrolyzer/model/h2.esdl index 562c7bb9c..13425926f 100644 --- a/tests/models/unit_cases_electricity/electrolyzer/model/h2.esdl +++ b/tests/models/unit_cases_electricity/electrolyzer/model/h2.esdl @@ -3,7 +3,7 @@ - + diff --git a/tests/test_electrolyzer.py b/tests/test_electrolyzer.py index 25501a14a..c320fcfd7 100644 --- a/tests/test_electrolyzer.py +++ b/tests/test_electrolyzer.py @@ -49,7 +49,7 @@ def energy_system_options(self): results = solution.extract_results() - gas_price_profile = "gas.price_profile" + gas_price_profile = "Hydrogen.price_profile" state = "GasDemand_0cf3.Gas_demand_mass_flow" nominal = solution.variable_nominal(state) * np.median( solution.get_timeseries(gas_price_profile).values From 792aa86ebf7bdc53ee89683f9367e0594609abc8 Mon Sep 17 00:00:00 2001 From: Rojer Date: Fri, 6 Sep 2024 15:40:01 +0200 Subject: [PATCH 7/9] style fixes --- src/mesido/esdl/esdl_heat_model.py | 14 +++---- src/mesido/esdl/esdl_model_base.py | 37 +++++++++++------- src/mesido/financial_mixin.py | 63 ++++++++++++++++-------------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index a74d9813f..79ce73f46 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -128,7 +128,7 @@ def get_carrier_id(self, asset, node=False, n=0): and asset.out_ports is not None and len(asset.in_ports) >= 2 and len(asset.out_ports) >= 2 - ): ## heat pump and heat exchanger + ): # heat pump and heat exchanger carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] if "Prim" in asset.in_ports[0].name: prim_in_id = carrier["id_number_mapping"] @@ -174,13 +174,13 @@ def get_carrier_id(self, asset, node=False, n=0): id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}Out" ids[port] = dict(carrier_id=id_number) - except: + except KeyError: try: carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}In" ids[port] = dict(carrier_id=id_number) - except: + except KeyError: carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}Out" @@ -194,14 +194,14 @@ def get_carrier_id(self, asset, node=False, n=0): id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}Conn[{k+1}]" ids[port] = dict(carrier_id=id_number) - except: + except KeyError: pass try: carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] id_number = carrier["id_number_mapping"] port = f"{carrier['type'].capitalize()}Conn[{k+1}]" ids[port] = dict(carrier_id=id_number) - except: + except KeyError: pass return ids @@ -619,7 +619,7 @@ def convert_node(self, asset: Asset) -> Tuple[Type[Node], MODIFIERS]: try: carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - except: + except KeyError: carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] modifiers = dict( @@ -1809,7 +1809,7 @@ def convert_electricity_node(self, asset: Asset) -> Tuple[Type[ElectricityNode], try: carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - except: + except KeyError: carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] modifiers = dict( diff --git a/src/mesido/esdl/esdl_model_base.py b/src/mesido/esdl/esdl_model_base.py index 53e29ccdf..b4be501c1 100644 --- a/src/mesido/esdl/esdl_model_base.py +++ b/src/mesido/esdl/esdl_model_base.py @@ -356,8 +356,10 @@ def _esdl_convert( ): self.connect(getattr(component, node_suf)[i], port_map[connected_to.id]) model_connections.append( - [getattr(component, node_suf)[i].name, - port_map[connected_to.id].name] + [ + getattr(component, node_suf)[i].name, + port_map[connected_to.id].name, + ] ) elif connected_to.id not in list(port_map.keys()): # If The asset is not in the @@ -381,8 +383,12 @@ def _esdl_convert( getattr(getattr(self, connected_node_asset.name), node_suf)[idx], ) model_connections.append( - [getattr(component, node_suf)[i], - getattr(getattr(self, connected_node_asset.name), node_suf)[idx]] + [ + getattr(component, node_suf)[i], + getattr(getattr(self, connected_node_asset.name), node_suf)[ + idx + ], + ] ) else: # If the Connected asset is not of type pipe, there might be @@ -430,10 +436,12 @@ def _esdl_convert( ], ) model_connections.append( - [getattr(component, elec_node_suf)[i], - getattr(getattr(self, connected_node_asset.name), elec_node_suf)[ - idx - ]] + [ + getattr(component, elec_node_suf)[i], + getattr( + getattr(self, connected_node_asset.name), elec_node_suf + )[idx], + ] ) else: self.connect_logical_links( @@ -441,8 +449,7 @@ def _esdl_convert( port_map[connected_to.id], ) model_connections.append( - [getattr(component, elec_node_suf)[i], - port_map[connected_to.id]] + [getattr(component, elec_node_suf)[i], port_map[connected_to.id]] ) connections.add(conn) i += 1 @@ -481,10 +488,12 @@ def _esdl_convert( ], ) model_connections.append( - [getattr(component, gas_node_suf)[i], - getattr(getattr(self, connected_node_asset.name), gas_node_suf)[ - idx - ]] + [ + getattr(component, gas_node_suf)[i], + getattr(getattr(self, connected_node_asset.name), gas_node_suf)[ + idx + ], + ] ) else: self.connect_logical_links( diff --git a/src/mesido/financial_mixin.py b/src/mesido/financial_mixin.py index 1f5786968..ee64074b6 100644 --- a/src/mesido/financial_mixin.py +++ b/src/mesido/financial_mixin.py @@ -97,9 +97,10 @@ def pre(self): parameters = self.parameters(0) bounds = self.bounds() - for owner, assets in self.energy_system_owners.items(): + for _owner, assets in self.energy_system_owners.items(): # All assets already have individual costs that can be used for the business cases - # Here we create revenue variables for the locations where energy is consumed or exhanged between owners + # Here we create revenue variables for the locations where energy is consumed or + # exhanged between owners # Create revenue variable for every demand type asset # The assumption is that if an organizational asset own the demand asset it has the @@ -111,8 +112,8 @@ def pre(self): *self.energy_system_components.get("electricity_demand", []), ]: # We check if we can find the carrier information used for that demand - # Note that we thus assume that the get_{commodity}_carriers() functions are set. - # In most practical cases this means using the ESDLExtraVarsMixin + # Note that we thus assume that the get_{commodity}_carriers() functions are + # set. In most practical cases this means using the ESDLExtraVarsMixin var_name = None nominal_power = None carrier_name = None @@ -122,14 +123,15 @@ def pre(self): attr["id_number_mapping"] == parameters[f"{asset}.ElectricityIn.carrier_id"] ): - # Note that we are including the port name at the end as we can have multiple revenue - # variables for a single asset as there can be multiple connections with different - # organizations over the different ports. + # Note that we are including the port name at the end as we can have + # multiple revenue variables for a single asset as there can be + # multiple connections with different organizations over the different + # ports. var_name = f"{asset}__revenue_ElectricityIn" carrier_name = attr["name"] nominal_power = self.variable_nominal(f"{asset}.Electricity_demand") - # Here we same the variable which should be used for the energy flow between - # the organizations and thus for computing the revenue. + # Here we same the variable which should be used for the energy flow + # between the organizations and thus for computing the revenue. self._asset_revenue_variable_port_map[var_name] = ( f"{asset}.ElectricityIn.Power" ) @@ -160,15 +162,16 @@ def pre(self): self.__asset_revenue_nominals[var_name] = avg_price * nominal_power self.__asset_revenue_bounds[var_name] = (0.0, np.inf) - # Find the assets that exchange energy with other owners and create a revenue variable for them as well - # We loop over the assets + # Find the assets that exchange energy with other owners and create a revenue variable + # for them as well We loop over the assets for asset in assets: # This is something extra I created. It is a nested list in the form of: # [[asset1.HeatIn, asset2.HeatOut], [asset3.HeatIn, asset4.HeatOut], ...] conn = self.get_connections() # We loop over the connections (yes this is inefficient) for connection in conn: - # Here we check if our asset is in the connection hence we find a connected asset + # Here we check if our asset is in the connection hence we find a connected + # asset if asset == connection[0].split(".")[0] or asset == connection[1].split(".")[0]: other_asset = ( connection[0].split(".")[0] @@ -186,10 +189,10 @@ def pre(self): for x in temp[1:]: port = port + "." + x - # Here we check that the connected asset is not also owned by the same entity. - # If the other asset belongs to an organization we create a revenue variable - # to represent the cashflow associated with the energy exchanged between the two - # organizations. + # Here we check that the connected asset is not also owned by the same + # entity. If the other asset belongs to an organization we create a + # revenue variable to represent the cashflow associated with the energy + # exchanged between the two organizations. if other_asset not in assets: var_name = f"{asset}__revenue_{port}" nominal_power = None @@ -226,8 +229,8 @@ def pre(self): carrier_name = attr["name"] if carrier_name is not None: # Please note that we create a dict mapping with a list. As a single - # asset can have multiple revenue variables as it can have connections with - # other organizations on different ports. + # asset can have multiple revenue variables as it can have + # connections with other organizations on different ports. try: self._asset_revenue_map[asset].append(var_name) except KeyError: @@ -237,7 +240,9 @@ def pre(self): price_profile = self.get_timeseries( f"{carrier_name}.price_profile" ).values - except NameError or KeyError: + except NameError: + price_profile = np.ones(len(self.times())) + except KeyError: price_profile = np.ones(len(self.times())) avg_price = ( np.mean(price_profile) if np.mean(price_profile) else 1.0 @@ -1434,22 +1439,20 @@ def __revenue_constraints(self, ensemble_member): for variable_revenue_var in variable_revenue_vars: carrier_name = None port = None - # We find the associated carrier and port (is the full string with asset name included) + # We find the associated carrier and port (is the full string with asset name + # included) + port_name = self._asset_revenue_variable_port_map[variable_revenue_var] for _id, attr in self.get_electricity_carriers().items(): if ( attr["id_number_mapping"] - == parameters[ - f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" - ] + == parameters[f"{asset}.{port_name.split('.')[-2]}.carrier_id"] ): carrier_name = attr["name"] port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" for _id, attr in self.get_gas_carriers().items(): if ( attr["id_number_mapping"] - == parameters[ - f"{asset}.{self._asset_revenue_variable_port_map[variable_revenue_var].split('.')[-2]}.carrier_id" - ] + == parameters[f"{asset}.{port_name.split('.')[-2]}.carrier_id"] ): carrier_name = attr["name"] port = f"{self._asset_revenue_variable_port_map[variable_revenue_var]}" @@ -1478,10 +1481,10 @@ def __revenue_constraints(self, ensemble_member): *self.energy_system_components.get("electricity_demand", []), ]: energy_flow_sign = -1.0 - # For nodes/busses we do this by checking the type of port in the topology object - # It is a bit of a hassle as we need to find the index of the port of the node - # for that we find the last digit (which by definition is the port index) in the - # port name string. + # For nodes/busses we do this by checking the type of port in the topology + # object. It is a bit of a hassle as we need to find the index of the port of + # the node for that we find the last digit (which by definition is the port + # index) in the port name string. if asset in self.energy_system_topology.nodes.keys(): port_number = int([x for x in port if x.isdigit()][-1]) if ( From 4aebe9297fad580ecc5d2a54c93f9b633d621837 Mon Sep 17 00:00:00 2001 From: Rojer Date: Sun, 8 Sep 2024 18:41:07 +0200 Subject: [PATCH 8/9] start with incorperating feedback Femke --- src/mesido/esdl/esdl_heat_model.py | 115 ++++++++++++----------------- 1 file changed, 48 insertions(+), 67 deletions(-) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index 79ce73f46..83ba1d066 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -121,88 +121,69 @@ def merge_modifiers(self, a: dict, b: dict): return b - def get_carrier_id(self, asset, node=False, n=0): - if not node: - if ( - asset.in_ports is not None - and asset.out_ports is not None - and len(asset.in_ports) >= 2 - and len(asset.out_ports) >= 2 - ): # heat pump and heat exchanger - carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - if "Prim" in asset.in_ports[0].name: - prim_in_id = carrier["id_number_mapping"] - else: - sec_in_id = carrier["id_number_mapping"] - carrier = asset.global_properties["carriers"][asset.in_ports[1].carrier.id] + def get_carrier_id(self, asset, n=0): + if ( + asset.in_ports is not None + and asset.out_ports is not None + and len(asset.in_ports) >= 2 + and len(asset.out_ports) >= 2 + ): # heat pump and heat exchanger + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + if "Prim" in asset.in_ports[0].name: + prim_in_id = carrier["id_number_mapping"] + else: + sec_in_id = carrier["id_number_mapping"] + carrier = asset.global_properties["carriers"][asset.in_ports[1].carrier.id] + if "Prim" in asset.in_ports[1].name: + prim_in_id = carrier["id_number_mapping"] + else: + sec_in_id = carrier["id_number_mapping"] + if len(asset.in_ports) == 3: if "Prim" in asset.in_ports[1].name: prim_in_id = carrier["id_number_mapping"] else: sec_in_id = carrier["id_number_mapping"] - if len(asset.in_ports) == 3: - if "Prim" in asset.in_ports[1].name: - prim_in_id = carrier["id_number_mapping"] - else: - sec_in_id = carrier["id_number_mapping"] - carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] - if "Prim" in asset.out_ports[0].name: - prim_out_id = carrier["id_number_mapping"] - else: - sec_out_id = carrier["id_number_mapping"] - carrier = asset.global_properties["carriers"][asset.out_ports[1].carrier.id] - if "Prim" in asset.out_ports[1].name: - prim_out_id = carrier["id_number_mapping"] - else: - sec_out_id = carrier["id_number_mapping"] - ids = dict( - Primary=dict( - HeatIn=dict(carrier_id=prim_in_id), HeatOut=dict(carrier_id=prim_out_id) - ), - Secondary=dict( - HeatIn=dict(carrier_id=sec_in_id), HeatOut=dict(carrier_id=sec_out_id) - ), - ) - return ids + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + if "Prim" in asset.out_ports[0].name: + prim_out_id = carrier["id_number_mapping"] else: - ids = dict() - try: - carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}In" - ids[port] = dict(carrier_id=id_number) - carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] - id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}Out" - ids[port] = dict(carrier_id=id_number) - except KeyError: - try: - carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] - id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}In" - ids[port] = dict(carrier_id=id_number) - except KeyError: - carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] - id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}Out" - ids[port] = dict(carrier_id=id_number) - return ids + sec_out_id = carrier["id_number_mapping"] + carrier = asset.global_properties["carriers"][asset.out_ports[1].carrier.id] + if "Prim" in asset.out_ports[1].name: + prim_out_id = carrier["id_number_mapping"] + else: + sec_out_id = carrier["id_number_mapping"] + ids = dict( + Primary=dict( + HeatIn=dict(carrier_id=prim_in_id), HeatOut=dict(carrier_id=prim_out_id) + ), + Secondary=dict( + HeatIn=dict(carrier_id=sec_in_id), HeatOut=dict(carrier_id=sec_out_id) + ), + ) + return ids else: ids = dict() - for k in range(0, n): + try: + carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}In" + ids[port] = dict(carrier_id=id_number) + carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] + id_number = carrier["id_number_mapping"] + port = f"{carrier['type'].capitalize()}Out" + ids[port] = dict(carrier_id=id_number) + except KeyError: try: carrier = asset.global_properties["carriers"][asset.in_ports[0].carrier.id] id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}Conn[{k+1}]" + port = f"{carrier['type'].capitalize()}In" ids[port] = dict(carrier_id=id_number) except KeyError: - pass - try: carrier = asset.global_properties["carriers"][asset.out_ports[0].carrier.id] id_number = carrier["id_number_mapping"] - port = f"{carrier['type'].capitalize()}Conn[{k+1}]" + port = f"{carrier['type'].capitalize()}Out" ids[port] = dict(carrier_id=id_number) - except KeyError: - pass return ids def get_asset_attribute_value( From 4ef1b91b299e25e7bc4dcd1b559736122b0586be Mon Sep 17 00:00:00 2001 From: Rojer Date: Sat, 14 Sep 2024 18:35:25 +0200 Subject: [PATCH 9/9] getting back the get_owner function --- src/mesido/esdl/esdl_heat_model.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/mesido/esdl/esdl_heat_model.py b/src/mesido/esdl/esdl_heat_model.py index 83ba1d066..b26218327 100644 --- a/src/mesido/esdl/esdl_heat_model.py +++ b/src/mesido/esdl/esdl_heat_model.py @@ -121,6 +121,15 @@ def merge_modifiers(self, a: dict, b: dict): return b + def get_owner(self, asset): + return dict( + owner=( + asset.attributes["isOwnedBy"].name + if asset.attributes["isOwnedBy"] is not None + else "NoOwner" + ) + ) + def get_carrier_id(self, asset, n=0): if ( asset.in_ports is not None