From ae3a6a02b5e2f5101e0c7d4974abf99ea0b9429c Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 09:37:36 +0100 Subject: [PATCH 01/19] Added possibility in controller to set the heatpump to bypass mode --- .../adapter/transforms/controller_mapper.py | 10 ++- .../controller/controller_heat_transfer.py | 42 +++++++----- .../assets/controller/controller_network.py | 22 ++++--- .../entities/assets/production_cluster.py | 6 +- .../entities/network_controller.py | 66 +++++++++++++++++-- 5 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/omotes_simulator_core/adapter/transforms/controller_mapper.py b/src/omotes_simulator_core/adapter/transforms/controller_mapper.py index 8fdfaaa3..be785de9 100644 --- a/src/omotes_simulator_core/adapter/transforms/controller_mapper.py +++ b/src/omotes_simulator_core/adapter/transforms/controller_mapper.py @@ -158,9 +158,15 @@ def to_entity( "The network is looped via the heat pumps and heat exchangers, " "which is not supported." ) + # find first networks with no buffers + for j in range(len(networks)): + if not networks[j].storages: + break - for i in range(1, len(networks)): - networks[i].path = graph.get_path(str(i), "0") + for i in range(len(networks)): + if i == j: + continue + networks[i].path = graph.get_path(str(i), str(j)) if len(networks[i].path) > 3: raise RuntimeError( "The network is connected via more then two stages which is not supported." diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index f7ce1e68..fe77bae5 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -41,26 +41,34 @@ def __init__(self, name: str, identifier: str, factor: float): super().__init__(name, identifier) self.factor = factor - def set_asset(self, heat_demand: float) -> dict[str, dict[str, float]]: + def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[str, float]]: """Method to set the asset to the given heat demand. The supply and return temperatures are also set. :param float heat_demand: Heat demand to set. + :param bypass: When true the heat exchange is bypassed, so the heat demand is not reduced by the factor. Default is False. """ - # TODO set correct values also for prim and secondary side. - return { - self.id: { - PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, - PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 30, - PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, - SECONDARY - + PROPERTY_HEAT_DEMAND: np.abs(heat_demand) - * self.factor - * ( - np.sign(heat_demand) * -1 - ), # Invert sign of secondary heat demand, as it is opposite to primary side. - SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, - SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, - PROPERTY_SET_PRESSURE: False, + if bypass: + return { + self.id: { + PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, + PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 50, + PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 80, + SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * 1, + SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, + SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, + PROPERTY_SET_PRESSURE: False, + } + } + else: + return { + self.id: { + PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, + PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 30, + PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, + SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * self.factor, + SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, + SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, + PROPERTY_SET_PRESSURE: False, + } } - } diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 17442f89..5cdade65 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -16,6 +16,8 @@ import datetime +from numpy.ma.core import product + from omotes_simulator_core.entities.assets.asset_defaults import ( PROPERTY_HEAT_DEMAND, PROPERTY_SET_PRESSURE, @@ -49,7 +51,7 @@ class ControllerNetwork: """List of all producers in the network.""" storages: list[ControllerAtesStorage | ControllerIdealHeatStorage] """List of all storages in the network.""" - factor_to_first_network: float + factor_to_first_network: list[float] """Factor to calculate power in the first network in the list of networks.""" path: list[str] """Path from this network to the first network in the total system.""" @@ -69,7 +71,7 @@ def __init__( self.consumers = consumers_in self.producers = producers_in self.storages = storages_in - self.factor_to_first_network = factor_to_first_network + self.factor_to_first_network = [factor_to_first_network] self.path: list[str] = [] def exists(self, identifier: str) -> bool: @@ -91,9 +93,8 @@ def exists(self, identifier: str) -> bool: def get_total_heat_demand(self, time: datetime.datetime) -> float: """Method which the total heat demand at the given time corrected to the first network.""" - return ( - sum([consumer.get_heat_demand(time) for consumer in self.consumers]) - * self.factor_to_first_network + return sum([consumer.get_heat_demand(time) for consumer in self.consumers]) * product( + self.factor_to_first_network ) def get_total_discharge_storage(self) -> float: @@ -121,9 +122,8 @@ def get_total_supply(self) -> float: :return float: Total heat supply of all producers. """ - return ( - float(sum([producer.power for producer in self.producers])) - * self.factor_to_first_network + return float(sum([producer.power for producer in self.producers])) * product( + self.factor_to_first_network ) def set_supply_to_max(self, priority: int = 0) -> dict: @@ -233,8 +233,10 @@ def set_pressure(self) -> str: The network can thus pass back the id for which asset the pressure needs to be set. The controller can then do this. """ - if self.heat_transfer_assets_sec: - return self.heat_transfer_assets_sec[0].id if self.producers: return self.producers[0].id + if self.heat_transfer_assets_sec: + return self.heat_transfer_assets_sec[0].id + if self.heat_transfer_assets_prim: + return self.heat_transfer_assets_prim[0].id raise ValueError("No asset found for which the pressure can be set.") diff --git a/src/omotes_simulator_core/entities/assets/production_cluster.py b/src/omotes_simulator_core/entities/assets/production_cluster.py index 24567386..74fbcecb 100644 --- a/src/omotes_simulator_core/entities/assets/production_cluster.py +++ b/src/omotes_simulator_core/entities/assets/production_cluster.py @@ -124,8 +124,8 @@ def _set_heat_demand(self, heat_demand: float) -> None: """ # Calculate the mass flow rate self.heat_demand_set_point = heat_demand - self.controlled_mass_flow = heat_demand_and_temperature_to_mass_flow( - thermal_demand=-1 * heat_demand, + self.controlled_mass_flow = -heat_demand_and_temperature_to_mass_flow( + thermal_demand=heat_demand, temperature_in=self.temperature_in, temperature_out=self.temperature_out, ) @@ -248,7 +248,7 @@ def is_converged(self) -> bool: :return: True if the asset has converged, False otherwise """ if self.solver_asset.pre_scribe_mass_flow: # type: ignore - return abs(self.get_actual_heat_supplied() - self.heat_demand_set_point) < ( + return abs(self.get_actual_heat_supplied() + self.heat_demand_set_point) < ( abs(self.heat_demand_set_point * 0.001) ) else: diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index 7e5d5392..0c89a6ae 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -12,7 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Module for new controller which can also cope with Heat pumps and heat exchangers.""" +"""Module for controller which can also cope with Heat pumps and heat exchangers.""" import datetime import logging @@ -53,19 +53,19 @@ def update_networks_factor(self) -> None: """Method to update the factor of the networks taken into account the changing COP.""" for network in self.networks: current_network = network - network.factor_to_first_network = 1 + network.factor_to_first_network = [1] for step in network.path: if current_network == self.networks[int(step)]: continue for asset in current_network.heat_transfer_assets_prim: if self.networks[int(step)].exists(asset.id): - network.factor_to_first_network *= asset.factor - current_network = self.networks[int(step)] + network.factor_to_first_network.append(asset.factor) + # current_network = self.networks[int(step)] break for asset in current_network.heat_transfer_assets_sec: if self.networks[int(step)].exists(asset.id): - network.factor_to_first_network /= asset.factor - current_network = self.networks[int(step)] + network.factor_to_first_network.append(1 / asset.factor) + # current_network = self.networks[int(step)] break def update_setpoints(self, time: datetime.datetime) -> dict: @@ -86,6 +86,50 @@ def update_setpoints(self, time: datetime.datetime) -> dict: self.update_networks_factor() total_demand = sum([network.get_total_heat_demand(time) for network in self.networks]) total_supply = sum([network.get_total_supply() for network in self.networks]) + if total_supply > total_demand: + # total supply is larger than demand, so demand can be set to required demand. + consumers = self._set_consumer_to_demand(time) + surplus_supply = total_supply - total_demand + # Check charge capacity from storage + total_charge_storage = sum( + [network.get_total_charge_storage() for network in self.networks] + ) + if total_charge_storage > surplus_supply: + # there is more charge capacity than surplus supply, so we can set source to max and storages to charge with the surplus supply. + producers = self._set_producers_to_max() + storages = self._set_storages_charge_power(surplus_supply) + else: + # The storage can charge to max. The sources need to be capped. + storages = self._set_all_storages_charge_to_max() + producers = self._set_producers_based_on_priority( + surplus_supply + total_charge_storage + ) + else: + # total supply is lower than demand, so we need to check if there is enough discharge capacity from storage. + total_discharge_storage = sum( + [network.get_total_discharge_storage() for network in self.networks] + ) + if (total_supply + total_discharge_storage) <= total_demand: + logger.warning( + f"Total supply + storage is lower than total demand at time: {time}" + f"Consumers are capped to the available power." + ) + factor = (total_supply + total_discharge_storage) / total_demand + producers = self._set_producers_to_max() + + storages = self._set_all_storages_discharge_to_max() + consumers = self._set_consumer_to_demand(time, factor=factor) + else: + # there is enough supply + storage to cover the demand. sources to max and storages to deliver the rest. + consumers = self._set_consumer_to_demand(time) + surplus_demand = total_supply - total_demand + producers = self._set_producers_to_max() + storages = self._set_storages_discharge_power(surplus_demand) + producers.update(consumers) + producers.update(storages) + + # Getting the settings for the heat transfer assets + heat_transfer = {} total_charge_storage = sum( [network.get_total_charge_storage() for network in self.networks] ) @@ -165,8 +209,16 @@ def update_setpoints(self, time: datetime.datetime) -> dict: # this might look weird, but we know there is only one primary or secondary asset. # So we can directly set it. for asset in network.heat_transfer_assets_prim: - heat_transfer.update(asset.set_asset(total_heat_supply)) + if total_heat_supply > 0: + heat_transfer.update(asset.set_asset(total_heat_supply)) + else: + heat_transfer.update(asset.set_asset(total_heat_supply, True)) for asset in network.heat_transfer_assets_sec: + if total_heat_supply > 0: + heat_transfer.update(asset.set_asset(-total_heat_supply, True)) + else: + heat_transfer.update(asset.set_asset(-total_heat_supply)) + producers.update(heat_transfer) heat_transfer.update(asset.set_asset(-total_heat_supply)) # Update the asset setpoints with the heat transfer setpoints. From 085e6db6f075249cd0ea022e335fdf74436408c7 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 13:15:49 +0100 Subject: [PATCH 02/19] Fixed two small errors which occured due to rebasing --- .../assets/controller/controller_network.py | 14 ++++++-------- .../entities/network_controller.py | 2 -- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 5cdade65..eb655050 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -102,20 +102,18 @@ def get_total_discharge_storage(self) -> float: :return float: Total heat discharge of all storages. """ - return ( - float(sum([storage.effective_max_discharge_power for storage in self.storages])) - * self.factor_to_first_network - ) + return float( + sum([storage.effective_max_discharge_power for storage in self.storages]) + ) * product(self.factor_to_first_network) def get_total_charge_storage(self) -> float: """Method to get the total storage charge of the network corrected to the first network. :return float: Total heat charge of all storages. """ - return ( - float(sum([storage.effective_max_charge_power for storage in self.storages])) - * self.factor_to_first_network - ) + return float( + sum([storage.effective_max_charge_power for storage in self.storages]) + ) * product(self.factor_to_first_network[:-2]) def get_total_supply(self) -> float: """Method to get the total heat supply of the network. diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index 0c89a6ae..f6766236 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -218,8 +218,6 @@ def update_setpoints(self, time: datetime.datetime) -> dict: heat_transfer.update(asset.set_asset(-total_heat_supply, True)) else: heat_transfer.update(asset.set_asset(-total_heat_supply)) - producers.update(heat_transfer) - heat_transfer.update(asset.set_asset(-total_heat_supply)) # Update the asset setpoints with the heat transfer setpoints. asset_setpoints.update(heat_transfer) From 30065d2a4ec73866e7cda9880946103a42b2d20c Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 13:40:23 +0100 Subject: [PATCH 03/19] Small fixes in the controller to get the correct output --- .../controller/controller_heat_transfer.py | 2 +- .../assets/controller/controller_network.py | 4 + .../entities/network_controller.py | 76 ++++--------------- 3 files changed, 20 insertions(+), 62 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index fe77bae5..d1b655a0 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -54,7 +54,7 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 50, PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 80, - SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * 1, + SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * -1, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, PROPERTY_SET_PRESSURE: False, diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index eb655050..56a55fc9 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -164,6 +164,8 @@ def set_storage_charge_power(self, factor: float = 1) -> dict: for storage in self.storages: storage_settings[storage.id] = { PROPERTY_HEAT_DEMAND: +1 * storage.effective_max_charge_power * factor, + PROPERTY_TEMPERATURE_OUT: storage.temperature_out, + PROPERTY_TEMPERATURE_IN: storage.temperature_in, } return storage_settings @@ -178,6 +180,8 @@ def set_storage_discharge_power(self, factor: float = 1) -> dict: # Discharging is negative (e.g., heat from component/system to the network) storage_settings[storage.id] = { PROPERTY_HEAT_DEMAND: -1 * storage.effective_max_discharge_power * factor, + PROPERTY_TEMPERATURE_OUT: storage.temperature_out, + PROPERTY_TEMPERATURE_IN: storage.temperature_in, } return storage_settings diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index f6766236..78d67976 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -86,9 +86,14 @@ def update_setpoints(self, time: datetime.datetime) -> dict: self.update_networks_factor() total_demand = sum([network.get_total_heat_demand(time) for network in self.networks]) total_supply = sum([network.get_total_supply() for network in self.networks]) + + # Initialize the producer, consumer, and storage setpoints dicts. + producer_setpoints: AssetSetpointsDict = {} + consumer_setpoints: AssetSetpointsDict = {} + storage_setpoints: AssetSetpointsDict = {} if total_supply > total_demand: # total supply is larger than demand, so demand can be set to required demand. - consumers = self._set_consumer_to_demand(time) + consumer_setpoints = self._set_consumer_to_demand(time) surplus_supply = total_supply - total_demand # Check charge capacity from storage total_charge_storage = sum( @@ -96,12 +101,12 @@ def update_setpoints(self, time: datetime.datetime) -> dict: ) if total_charge_storage > surplus_supply: # there is more charge capacity than surplus supply, so we can set source to max and storages to charge with the surplus supply. - producers = self._set_producers_to_max() - storages = self._set_storages_charge_power(surplus_supply) + producer_setpoints = self._set_producers_to_max() + storage_setpoints = self._set_storages_charge_power(surplus_supply) else: # The storage can charge to max. The sources need to be capped. - storages = self._set_all_storages_charge_to_max() - producers = self._set_producers_based_on_priority( + storage_setpoints = self._set_all_storages_charge_to_max() + producer_setpoints = self._set_producers_based_on_priority( surplus_supply + total_charge_storage ) else: @@ -115,67 +120,16 @@ def update_setpoints(self, time: datetime.datetime) -> dict: f"Consumers are capped to the available power." ) factor = (total_supply + total_discharge_storage) / total_demand - producers = self._set_producers_to_max() + producer_setpoints = self._set_producers_to_max() - storages = self._set_all_storages_discharge_to_max() - consumers = self._set_consumer_to_demand(time, factor=factor) + storage_setpoints = self._set_all_storages_discharge_to_max() + consumer_setpoints = self._set_consumer_to_demand(time, factor=factor) else: # there is enough supply + storage to cover the demand. sources to max and storages to deliver the rest. - consumers = self._set_consumer_to_demand(time) + consumer_setpoints = self._set_consumer_to_demand(time) surplus_demand = total_supply - total_demand - producers = self._set_producers_to_max() - storages = self._set_storages_discharge_power(surplus_demand) - producers.update(consumers) - producers.update(storages) - - # Getting the settings for the heat transfer assets - heat_transfer = {} - total_charge_storage = sum( - [network.get_total_charge_storage() for network in self.networks] - ) - total_discharge_storage = sum( - [network.get_total_discharge_storage() for network in self.networks] - ) - - # Initialize the producer, consumer, and storage setpoints dicts. - producer_setpoints: AssetSetpointsDict = {} - consumer_setpoints: AssetSetpointsDict = {} - storage_setpoints: AssetSetpointsDict = {} - - if (total_supply + total_discharge_storage) <= total_demand: - logger.warning( - "Total supply + storage is lower than total demand at time: %s" - "Consumers are capped to the available power.", - time, - ) - factor = (total_supply + total_discharge_storage) / total_demand - # Define setpoints - producer_setpoints = self._set_producers_to_max() - storage_setpoints = self._set_all_storages_discharge_to_max() - consumer_setpoints = self._set_consumer_to_demand(time, factor=factor) - else: - # Set consumer to requested demand. - consumer_setpoints = self._set_consumer_to_demand(time, factor=1.0) - # Set producers and storages based on the supply and demand, and the charge and - # discharge capacity of the storage. - if total_supply >= total_demand: - # there is a surplus of supply we can charge the storage, storage becomes consumer. - surplus_supply = total_supply - total_demand - if surplus_supply <= total_charge_storage: - storage_setpoints = self._set_storages_charge_power(surplus_supply) - producer_setpoints = self._set_producers_to_max() - elif surplus_supply > total_charge_storage: - # need to cap the power of the source based on priority - storage_setpoints = self._set_storages_charge_power(total_charge_storage) - producer_setpoints = self._set_producers_based_on_priority( - total_demand + total_charge_storage - ) - else: - # there is a deficit of supply we can discharge the storage, storage becomes - # producer. - deficit_supply = total_demand - total_supply - storage_setpoints = self._set_storages_discharge_power(deficit_supply) producer_setpoints = self._set_producers_to_max() + storage_setpoints = self._set_storages_discharge_power(surplus_demand) # Update the asset setpoints with the setpoints of the producers, consumers, # and storages. From 0d2ff22cdcc8ebf1ee412327fcc24a83caab3829 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 14:12:27 +0100 Subject: [PATCH 04/19] Added bypass mode to heatpump --- .../entities/assets/heat_pump.py | 18 ++++- .../network/assets/heat_transfer_asset.py | 71 +++++++++++++------ 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 6f298ab4..f5104555 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -63,6 +63,12 @@ class HeatPump(AssetAbstract): and the pressure is predescribed. """ + control_mass_flow_primary: bool + """Flag to indicate whether the mass flow rate on the primary side is controlled. + If True, the mass flow rate is controlled. If False, the mass flow rate is not controlled + and the pressure is predescribed. + """ + coefficient_of_performance: float """Coefficient of perfomance for the heat pump.""" @@ -129,7 +135,10 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: temperature_in=self.temperature_in_secondary, temperature_out=self.temperature_out_secondary, ) - self.control_mass_flow_secondary = not (setpoints_secondary[PROPERTY_SET_PRESSURE]) + self.control_mass_flow_secondary = not ( + setpoints_secondary[PROPERTY_SET_PRESSURE] + & (setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND] < 0) + ) # Assign setpoints to the HeatTransferAsset solver asset self.solver_asset.temperature_in_secondary = self.temperature_in_secondary # type: ignore @@ -176,6 +185,10 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: temperature_in=self.temperature_in_primary, temperature_out=self.temperature_out_primary, ) + self.control_mass_flow_primary = not ( + setpoints_primary[PROPERTY_SET_PRESSURE] + & (setpoints_primary[PRIMARY + PROPERTY_HEAT_DEMAND] < 0) + ) # Assign setpoints to the HeatTransferAsset solver asset self.solver_asset.temperature_in_primary = self.temperature_in_primary # type: ignore @@ -183,6 +196,9 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: self.solver_asset.mass_flow_initialization_primary = ( # type: ignore self.mass_flow_initialization_primary ) + self.solver_asset.pre_scribe_mass_flow_primary = ( # type: ignore + self.control_mass_flow_primary + ) def set_setpoints(self, setpoints: Dict) -> None: """Placeholder to set the setpoints of an asset prior to a simulation. diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index 8f17d98f..b1a49e27 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -44,6 +44,7 @@ def __init__( mass_flow_initialization_primary: float = -20.0, heat_transfer_coefficient: float = 1.0, pre_scribe_mass_flow_secondary: bool = False, + pre_scribe_mass_flow_primary: bool = False, temperature_out_secondary: float = 293.15, mass_flow_rate_set_point_secondary: float = -80.0, pressure_set_point_secondary: float = 10000.0, @@ -94,6 +95,9 @@ def __init__( # Define the flag that indicates whether the mass flow rate or the pressure is prescribed # at the hot side of the heat pump self.pre_scribe_mass_flow_secondary = pre_scribe_mass_flow_secondary + # Define the flag that indicates whether the mass flow rate or the pressure is prescribed + # at the cold side of the heat pump + self.pre_scribe_mass_flow_primary = pre_scribe_mass_flow_primary # Define the mass flow rate set point for the asset on the secondary side self.mass_flow_rate_rate_set_point_secondary = mass_flow_rate_set_point_secondary # Define the pressure set point for the asset @@ -382,34 +386,57 @@ def get_equations(self) -> list[EquationObject]: self.secondary_side_outflow, ]: equations.append(self.get_press_to_node_equation(connection_point=connection_point)) - - # -- Internal continuity (1x) -- - # Add the internal continuity equation at the primary side. - equations.append( - self.add_continuity_equation( - connection_point_1=self.primary_side_inflow, - connection_point_2=self.primary_side_outflow, - ) - ) - # -- Energy balance equation for the heat transfer asset (1x) -- - # Defines the energy balance between the primary and secondary side of the - # heat transfer asset. - # If the mass flow at the inflow node of the primary and secondary side is not zero, - if (iteration_flow_direction_primary != FlowDirection.ZERO) or ( - iteration_flow_direction_secondary != FlowDirection.ZERO - ): + if self.pre_scribe_mass_flow_primary: + # -- Internal continuity (1x) -- + # Add the internal continuity equation at the primary side. equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=self.primary_side_inflow, - mass_flow_value=self.get_mass_flow_from_prev_solution(), + self.add_continuity_equation( + connection_point_1=self.primary_side_inflow, + connection_point_2=self.primary_side_outflow, ) ) - # If the mass flow at the inflow node of the primary and secondary side is zero, + # -- Energy balance equation for the heat transfer asset (1x) -- + # Defines the energy balance between the primary and secondary side of the + # heat transfer asset. + # If the mass flow at the inflow node of the primary and secondary side is not zero, + if (iteration_flow_direction_primary != FlowDirection.ZERO) or ( + iteration_flow_direction_secondary != FlowDirection.ZERO + ): + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=self.primary_side_inflow, + mass_flow_value=self.get_mass_flow_from_prev_solution(), + ) + ) + # If the mass flow at the inflow node of the primary and secondary side is zero, + else: + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=self.primary_side_inflow, + mass_flow_value=0, + ) + ) else: + if iteration_flow_direction_secondary == FlowDirection.ZERO: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary + else: + if iteration_flow_direction_secondary == FlowDirection.POSITIVE: + pset_out = self.pressure_set_point_secondary / 2 + pset_in = self.pressure_set_point_secondary + else: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary / 2 equations.append( - self.prescribe_mass_flow_at_connection_point( + self.prescribe_pressure_at_connection_point( connection_point=self.primary_side_inflow, - mass_flow_value=0, + pressure_value=pset_in, + ) + ) + equations.append( + self.prescribe_pressure_at_connection_point( + connection_point=self.primary_side_outflow, + pressure_value=pset_out, ) ) # Return the equations From 23d30256eb95f35fd77084bf855a9edd3d5267f4 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 17:03:21 +0100 Subject: [PATCH 05/19] Rewrote equations setting of heatpump. --- .../entities/assets/heat_pump.py | 6 +- .../network/assets/heat_transfer_asset.py | 252 ++++++++++++++---- 2 files changed, 209 insertions(+), 49 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index f5104555..6a8d1024 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -131,7 +131,7 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_OUT] self.mass_flow_secondary = heat_demand_and_temperature_to_mass_flow( - thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND], + thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND] * -1, temperature_in=self.temperature_in_secondary, temperature_out=self.temperature_out_secondary, ) @@ -145,7 +145,7 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: self.solver_asset.temperature_out_secondary = ( # type: ignore self.temperature_out_secondary ) - self.solver_asset.mass_flow_rate_secondary = self.mass_flow_secondary # type: ignore + self.solver_asset.mass_flow_rate_rate_set_point_secondary = self.mass_flow_secondary # type: ignore self.solver_asset.pre_scribe_mass_flow_secondary = ( # type: ignore self.control_mass_flow_secondary ) @@ -180,7 +180,7 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: # Assign setpoints to the HeatPump asset self.temperature_in_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_OUT] - self.mass_flow_initialization_primary = heat_demand_and_temperature_to_mass_flow( + self.mass_flow_initialization_primary = -heat_demand_and_temperature_to_mass_flow( thermal_demand=setpoints_primary[PRIMARY + PROPERTY_HEAT_DEMAND], temperature_in=self.temperature_in_primary, temperature_out=self.temperature_out_primary, diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index b1a49e27..438d24a4 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -104,9 +104,11 @@ def __init__( self.pressure_set_point_secondary = pressure_set_point_secondary # Define flow directions self.flow_direction_primary = self.flow_direction(self.mass_flow_initialization_primary) + self.iteration_flow_direction_primary = self.flow_direction_primary self.flow_direction_secondary = self.flow_direction( self.mass_flow_rate_rate_set_point_secondary ) + self.iteration_flow_direction_secondary = self.flow_direction_secondary # Define connection points ( self.primary_side_inflow, @@ -128,9 +130,9 @@ def flow_direction(self, mass_flow: float) -> FlowDirection: The flow direction of the heat transfer asset. """ if mass_flow > MASSFLOW_ZERO_LIMIT: - return FlowDirection.NEGATIVE - elif mass_flow < -MASSFLOW_ZERO_LIMIT: return FlowDirection.POSITIVE + elif mass_flow < -MASSFLOW_ZERO_LIMIT: + return FlowDirection.NEGATIVE else: return FlowDirection.ZERO @@ -165,54 +167,212 @@ def get_ordered_connection_point_list(self) -> list[int]: """ # Determine the connection points based on the flow direction if ( - self.flow_direction_primary == FlowDirection.NEGATIVE - and self.flow_direction_secondary == FlowDirection.POSITIVE + self.iteration_flow_direction_primary == FlowDirection.NEGATIVE + and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE ): return [1, 0, 2, 3] elif ( - self.flow_direction_primary == FlowDirection.POSITIVE - and self.flow_direction_secondary == FlowDirection.POSITIVE + self.iteration_flow_direction_primary == FlowDirection.POSITIVE + and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE ): return [0, 1, 2, 3] elif ( - self.flow_direction_primary == FlowDirection.POSITIVE - and self.flow_direction_secondary == FlowDirection.NEGATIVE + self.iteration_flow_direction_primary == FlowDirection.POSITIVE + and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE ): return [0, 1, 3, 2] elif ( - self.flow_direction_primary == FlowDirection.NEGATIVE - and self.flow_direction_secondary == FlowDirection.NEGATIVE + self.iteration_flow_direction_primary == FlowDirection.NEGATIVE + and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE ): return [1, 0, 3, 2] elif ( - self.flow_direction_primary == FlowDirection.ZERO - and self.flow_direction_secondary == FlowDirection.ZERO + self.iteration_flow_direction_primary == FlowDirection.ZERO + and self.iteration_flow_direction_secondary == FlowDirection.ZERO ): return [0, 1, 2, 3] elif ( - self.flow_direction_primary == FlowDirection.ZERO - and self.flow_direction_secondary == FlowDirection.POSITIVE + self.iteration_flow_direction_primary == FlowDirection.ZERO + and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE ): return [0, 1, 2, 3] elif ( - self.flow_direction_primary == FlowDirection.ZERO - and self.flow_direction_secondary == FlowDirection.NEGATIVE + self.iteration_flow_direction_primary == FlowDirection.ZERO + and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE ): return [0, 1, 3, 2] elif ( - self.flow_direction_primary == FlowDirection.POSITIVE - and self.flow_direction_secondary == FlowDirection.ZERO + self.iteration_flow_direction_primary == FlowDirection.POSITIVE + and self.iteration_flow_direction_secondary == FlowDirection.ZERO ): return [0, 1, 2, 3] elif ( - self.flow_direction_primary == FlowDirection.NEGATIVE - and self.flow_direction_secondary == FlowDirection.ZERO + self.iteration_flow_direction_primary == FlowDirection.NEGATIVE + and self.iteration_flow_direction_secondary == FlowDirection.ZERO ): return [1, 0, 2, 3] else: return [0, 1, 2, 3] def get_equations(self) -> list[EquationObject]: + equations = [] + # pressure to node equations + for connection_point in range(4): + equations.append(self.get_press_to_node_equation(connection_point=connection_point)) + + # Internal energy to node equations + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=0, + use_relative_indexing=True, + ) + ] + < 0.0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=0, + supply_temperature=self.temperature_out_primary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=1, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=1)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=1, + supply_temperature=self.temperature_out_primary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=2, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=2)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=2, + supply_temperature=self.temperature_out_secondary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=3, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=3, + supply_temperature=self.temperature_out_secondary, + ) + ) + + # set mass flow rate or pressure + if self.pre_scribe_mass_flow_secondary: + mset = self.mass_flow_rate_rate_set_point_secondary + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=2, + mass_flow_value=mset, + ) + ) + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=3, + mass_flow_value=mset * -1, + ) + ) + else: + if self.iteration_flow_direction_secondary == FlowDirection.ZERO: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary + else: + if self.iteration_flow_direction_secondary == FlowDirection.POSITIVE: + pset_out = self.pressure_set_point_secondary / 2 + pset_in = self.pressure_set_point_secondary + else: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary / 2 + equations.append( + self.prescribe_pressure_at_connection_point( + connection_point=2, + pressure_value=pset_in, + ) + ) + equations.append( + self.prescribe_pressure_at_connection_point( + connection_point=3, + pressure_value=pset_out, + ) + ) + # set mass flow rate or pressure + if self.pre_scribe_mass_flow_primary: + mset = self.mass_flow_rate_rate_set_point_secondary + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=0, + mass_flow_value=mset, + ) + ) + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=1, + mass_flow_value=mset * -1, + ) + ) + else: + if self.iteration_flow_direction_secondary == FlowDirection.ZERO: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary + else: + if self.iteration_flow_direction_secondary == FlowDirection.POSITIVE: + pset_out = self.pressure_set_point_secondary / 2 + pset_in = self.pressure_set_point_secondary + else: + pset_out = self.pressure_set_point_secondary + pset_in = self.pressure_set_point_secondary / 2 + equations.append( + self.prescribe_pressure_at_connection_point( + connection_point=0, + pressure_value=pset_in, + ) + ) + equations.append( + self.prescribe_pressure_at_connection_point( + connection_point=1, + pressure_value=pset_out, + ) + ) + return equations + + def get_equations_old(self) -> list[EquationObject]: r"""Return the heat transfer equations. The method returns the heat transfer equations for the heat transfer asset. @@ -266,15 +426,9 @@ def get_equations(self) -> list[EquationObject]: self.flow_direction_secondary = self.flow_direction( self.mass_flow_rate_rate_set_point_secondary ) - ( - self.primary_side_inflow, - self.primary_side_outflow, - self.secondary_side_inflow, - self.secondary_side_outflow, - ) = self.get_ordered_connection_point_list() - if np.all(np.abs(self.prev_sol[0:-1:3]) < MASSFLOW_ZERO_LIMIT): - iteration_flow_direction_primary = self.flow_direction( + if np.all(np.abs(self.prev_sol[0:-1:3]) > MASSFLOW_ZERO_LIMIT): + self.iteration_flow_direction_primary = self.flow_direction( self.prev_sol[ self.get_index_matrix( property_name="mass_flow_rate", @@ -283,7 +437,7 @@ def get_equations(self) -> list[EquationObject]: ) ] ) - iteration_flow_direction_secondary = self.flow_direction( + self.iteration_flow_direction_secondary = self.flow_direction( self.prev_sol[ self.get_index_matrix( property_name="mass_flow_rate", @@ -293,8 +447,15 @@ def get_equations(self) -> list[EquationObject]: ] ) else: - iteration_flow_direction_primary = self.flow_direction_primary - iteration_flow_direction_secondary = self.flow_direction_secondary + self.iteration_flow_direction_primary = self.flow_direction_primary + self.iteration_flow_direction_secondary = self.flow_direction_secondary + + ( + self.primary_side_inflow, + self.primary_side_outflow, + self.secondary_side_inflow, + self.secondary_side_outflow, + ) = self.get_ordered_connection_point_list() # Initialize the equations list equations = [] @@ -309,7 +470,8 @@ def get_equations(self) -> list[EquationObject]: ) # Add the internal energy equations at connection points 1, and 3 to set # the temperature through internal energy at the outlet of the heat transfer asset. - if iteration_flow_direction_primary != FlowDirection.ZERO: + if self.iteration_flow_direction_primary != FlowDirection.ZERO: + equations.append( self.prescribe_temperature_at_connection_point( connection_point=self.primary_side_outflow, @@ -322,7 +484,7 @@ def get_equations(self) -> list[EquationObject]: connection_point=self.primary_side_outflow ) ) - if iteration_flow_direction_secondary != FlowDirection.ZERO: + if self.iteration_flow_direction_secondary != FlowDirection.ZERO: equations.append( self.prescribe_temperature_at_connection_point( connection_point=self.secondary_side_outflow, @@ -338,28 +500,26 @@ def get_equations(self) -> list[EquationObject]: # -- Mass flow rate or pressure on secondary side (2x) -- # Prescribe the pressure at the secondary side of the heat transfer asset. if self.pre_scribe_mass_flow_secondary: - if iteration_flow_direction_secondary == FlowDirection.ZERO: - mset = 0.0 - else: - mset = self.mass_flow_rate_rate_set_point_secondary + + mset = self.mass_flow_rate_rate_set_point_secondary equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=self.secondary_side_inflow, - mass_flow_value=mset * self.flow_direction_secondary.value, + mass_flow_value=mset, ) ) equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=self.secondary_side_outflow, - mass_flow_value=mset * self.flow_direction_secondary.value * -1, + mass_flow_value=mset * -1, ) ) else: - if iteration_flow_direction_secondary == FlowDirection.ZERO: + if self.iteration_flow_direction_secondary == FlowDirection.ZERO: pset_out = self.pressure_set_point_secondary pset_in = self.pressure_set_point_secondary else: - if iteration_flow_direction_secondary == FlowDirection.POSITIVE: + if self.iteration_flow_direction_secondary == FlowDirection.POSITIVE: pset_out = self.pressure_set_point_secondary / 2 pset_in = self.pressure_set_point_secondary else: @@ -399,13 +559,13 @@ def get_equations(self) -> list[EquationObject]: # Defines the energy balance between the primary and secondary side of the # heat transfer asset. # If the mass flow at the inflow node of the primary and secondary side is not zero, - if (iteration_flow_direction_primary != FlowDirection.ZERO) or ( - iteration_flow_direction_secondary != FlowDirection.ZERO + if (self.iteration_flow_direction_primary != FlowDirection.ZERO) or ( + self.iteration_flow_direction_secondary != FlowDirection.ZERO ): equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=self.primary_side_inflow, - mass_flow_value=self.get_mass_flow_from_prev_solution(), + mass_flow_value=-1 * self.pre_scribe_mass_flow_primary, ) ) # If the mass flow at the inflow node of the primary and secondary side is zero, @@ -417,11 +577,11 @@ def get_equations(self) -> list[EquationObject]: ) ) else: - if iteration_flow_direction_secondary == FlowDirection.ZERO: + if self.iteration_flow_direction_secondary == FlowDirection.ZERO: pset_out = self.pressure_set_point_secondary pset_in = self.pressure_set_point_secondary else: - if iteration_flow_direction_secondary == FlowDirection.POSITIVE: + if self.iteration_flow_direction_secondary == FlowDirection.POSITIVE: pset_out = self.pressure_set_point_secondary / 2 pset_in = self.pressure_set_point_secondary else: From d6167d00695a0564dd97163bffba76c0ff79dc43 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Feb 2026 17:24:18 +0100 Subject: [PATCH 06/19] Corrected flow direction setting --- .../solver/network/assets/heat_transfer_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index 438d24a4..06e91966 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -300,13 +300,13 @@ def get_equations(self) -> list[EquationObject]: equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=2, - mass_flow_value=mset, + mass_flow_value=-mset, ) ) equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=3, - mass_flow_value=mset * -1, + mass_flow_value=mset, ) ) else: From 4300c0350137572f1a8453dc97c55073cf6fff1e Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 26 Feb 2026 09:57:55 +0100 Subject: [PATCH 07/19] Fixed unit test and made bug fixes based ont he test --- .../controller/controller_heat_transfer.py | 2 +- .../assets/controller/controller_network.py | 2 +- .../entities/assets/heat_exchanger.py | 2 +- .../entities/network_controller.py | 10 +-- .../network/assets/heat_transfer_asset.py | 2 +- .../controller/test_controller_network.py | 10 ++- .../controller/test_controller_new_class.py | 12 ++-- unit_test/entities/test_ates_cluster.py | 4 +- unit_test/entities/test_heat_exchanger.py | 14 +++- unit_test/entities/test_heat_pump.py | 27 ++++++-- unit_test/entities/test_production_cluster.py | 4 +- .../integration/test_heat_transfer_asset.py | 32 ++++++--- .../assets/test_heat_transfer_asset.py | 69 ++++++++++++------- 13 files changed, 126 insertions(+), 64 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index d1b655a0..6f5a8d71 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -66,7 +66,7 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 30, PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, - SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * self.factor, + SECONDARY + PROPERTY_HEAT_DEMAND: -heat_demand * self.factor, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, PROPERTY_SET_PRESSURE: False, diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 56a55fc9..68ddf0d3 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -113,7 +113,7 @@ def get_total_charge_storage(self) -> float: """ return float( sum([storage.effective_max_charge_power for storage in self.storages]) - ) * product(self.factor_to_first_network[:-2]) + ) * product(self.factor_to_first_network) def get_total_supply(self) -> float: """Method to get the total heat supply of the network. diff --git a/src/omotes_simulator_core/entities/assets/heat_exchanger.py b/src/omotes_simulator_core/entities/assets/heat_exchanger.py index 42a89599..a807dd73 100644 --- a/src/omotes_simulator_core/entities/assets/heat_exchanger.py +++ b/src/omotes_simulator_core/entities/assets/heat_exchanger.py @@ -210,7 +210,7 @@ def write_to_output(self) -> None: self.solver_asset.get_heat_power_primary() # type: ignore ), PROPERTY_HEAT_LOSS: ( - self.solver_asset.get_heat_power_primary() # type: ignore + -self.solver_asset.get_heat_power_primary() # type: ignore - self.solver_asset.get_heat_power_secondary() # type: ignore ), } diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index 78d67976..71d833ff 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -60,12 +60,12 @@ def update_networks_factor(self) -> None: for asset in current_network.heat_transfer_assets_prim: if self.networks[int(step)].exists(asset.id): network.factor_to_first_network.append(asset.factor) - # current_network = self.networks[int(step)] + current_network = self.networks[int(step)] break for asset in current_network.heat_transfer_assets_sec: if self.networks[int(step)].exists(asset.id): network.factor_to_first_network.append(1 / asset.factor) - # current_network = self.networks[int(step)] + current_network = self.networks[int(step)] break def update_setpoints(self, time: datetime.datetime) -> dict: @@ -107,7 +107,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict: # The storage can charge to max. The sources need to be capped. storage_setpoints = self._set_all_storages_charge_to_max() producer_setpoints = self._set_producers_based_on_priority( - surplus_supply + total_charge_storage + total_demand + total_charge_storage ) else: # total supply is lower than demand, so we need to check if there is enough discharge capacity from storage. @@ -127,7 +127,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict: else: # there is enough supply + storage to cover the demand. sources to max and storages to deliver the rest. consumer_setpoints = self._set_consumer_to_demand(time) - surplus_demand = total_supply - total_demand + surplus_demand = total_demand - total_supply producer_setpoints = self._set_producers_to_max() storage_setpoints = self._set_storages_discharge_power(surplus_demand) @@ -166,7 +166,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict: if total_heat_supply > 0: heat_transfer.update(asset.set_asset(total_heat_supply)) else: - heat_transfer.update(asset.set_asset(total_heat_supply, True)) + heat_transfer.update(asset.set_asset(-total_heat_supply, True)) for asset in network.heat_transfer_assets_sec: if total_heat_supply > 0: heat_transfer.update(asset.set_asset(-total_heat_supply, True)) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index 06e91966..ca678057 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -334,7 +334,7 @@ def get_equations(self) -> list[EquationObject]: ) # set mass flow rate or pressure if self.pre_scribe_mass_flow_primary: - mset = self.mass_flow_rate_rate_set_point_secondary + mset = self.mass_flow_initialization_primary equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=0, diff --git a/unit_test/entities/controller/test_controller_network.py b/unit_test/entities/controller/test_controller_network.py index f035b6dc..92103ef3 100644 --- a/unit_test/entities/controller/test_controller_network.py +++ b/unit_test/entities/controller/test_controller_network.py @@ -61,7 +61,7 @@ def test_init(self): self.assertEqual(self.controller_network.consumers, self.consumers) self.assertEqual(self.controller_network.producers, self.producers) self.assertEqual(self.controller_network.storages, self.storages) - self.assertEqual(self.controller_network.factor_to_first_network, self.factor) + self.assertEqual(self.controller_network.factor_to_first_network, [self.factor]) self.assertEqual(self.controller_network.path, []) def test_exists(self): @@ -248,9 +248,13 @@ def test_set_all_storages_discharge_to_max(self): { storage1.id: { PROPERTY_HEAT_DEMAND: -20, + PROPERTY_TEMPERATURE_OUT: 50, + PROPERTY_TEMPERATURE_IN: 40, }, storage2.id: { PROPERTY_HEAT_DEMAND: -25, + PROPERTY_TEMPERATURE_OUT: 50, + PROPERTY_TEMPERATURE_IN: 40, }, }, ) @@ -278,9 +282,13 @@ def test_set_all_storages_charge_to_max(self): { storage1.id: { PROPERTY_HEAT_DEMAND: 10, + PROPERTY_TEMPERATURE_OUT: 50, + PROPERTY_TEMPERATURE_IN: 40, }, storage2.id: { PROPERTY_HEAT_DEMAND: 15, + PROPERTY_TEMPERATURE_OUT: 50, + PROPERTY_TEMPERATURE_IN: 40, }, }, ) diff --git a/unit_test/entities/controller/test_controller_new_class.py b/unit_test/entities/controller/test_controller_new_class.py index aaf724d7..d984b39b 100644 --- a/unit_test/entities/controller/test_controller_new_class.py +++ b/unit_test/entities/controller/test_controller_new_class.py @@ -81,9 +81,9 @@ def test_update_networks_factor_prim(self): # act self.controller.update_networks_factor() # assert - self.assertEqual(self.network1.factor_to_first_network, 1) - self.assertEqual(self.network2.factor_to_first_network, 2) - self.assertEqual(self.network3.factor_to_first_network, 6) + self.assertEqual(self.network1.factor_to_first_network, [1]) + self.assertEqual(self.network2.factor_to_first_network, [1, 2]) + self.assertEqual(self.network3.factor_to_first_network, [1, 3, 2]) def test_update_networks_factor_sec(self): # arrange @@ -99,9 +99,9 @@ def test_update_networks_factor_sec(self): # act self.controller.update_networks_factor() # assert - self.assertEqual(self.network1.factor_to_first_network, 1) - self.assertEqual(self.network2.factor_to_first_network, 0.5) - self.assertEqual(self.network3.factor_to_first_network, 0.16666666666666666) + self.assertEqual(self.network1.factor_to_first_network, [1]) + self.assertEqual(self.network2.factor_to_first_network, [1, 0.5]) + self.assertEqual(self.network3.factor_to_first_network, [1, 0.3333333333333333, 0.5]) def setup_update_set_points(self): """Helper method to set up the networks with assets. diff --git a/unit_test/entities/test_ates_cluster.py b/unit_test/entities/test_ates_cluster.py index 43bc0698..b06059f8 100644 --- a/unit_test/entities/test_ates_cluster.py +++ b/unit_test/entities/test_ates_cluster.py @@ -74,7 +74,7 @@ def test_injection_ates(self) -> None: self.ates_cluster.set_setpoints(setpoints=setpoints) # Assert - self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 358.15, delta=0.1) + self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 358.6696, delta=0.1) self.assertAlmostEqual(self.ates_cluster.cold_well_temperature, 290.15, delta=0.1) def test_production_ates(self) -> None: @@ -93,5 +93,5 @@ def test_production_ates(self) -> None: self.ates_cluster.set_setpoints(setpoints=setpoints) # Assert - self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 355.54, delta=0.1) + self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 290.1549, delta=0.1) self.assertAlmostEqual(self.ates_cluster.cold_well_temperature, 308.17, delta=0.1) diff --git a/unit_test/entities/test_heat_exchanger.py b/unit_test/entities/test_heat_exchanger.py index 33fef9c7..0f0cba67 100644 --- a/unit_test/entities/test_heat_exchanger.py +++ b/unit_test/entities/test_heat_exchanger.py @@ -55,8 +55,12 @@ def setUp(self) -> None: self.heat_exchanger.solver_asset.get_index_matrix( property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) + ] = -2.0 + self.heat_exchanger.solver_asset.prev_sol[ + self.heat_exchanger.solver_asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=1, use_relative_indexing=False + ) ] = 2.0 - self.heat_exchanger.solver_asset.prev_sol[ self.heat_exchanger.solver_asset.get_index_matrix( property_name="internal_energy", connection_point=1, use_relative_indexing=False @@ -73,7 +77,11 @@ def setUp(self) -> None: property_name="mass_flow_rate", connection_point=2, use_relative_indexing=False ) ] = 1.0 - + self.heat_exchanger.solver_asset.prev_sol[ + self.heat_exchanger.solver_asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=3, use_relative_indexing=False + ) + ] = -1.0 self.heat_exchanger.solver_asset.prev_sol[ self.heat_exchanger.solver_asset.get_index_matrix( property_name="internal_energy", connection_point=3, use_relative_indexing=False @@ -89,6 +97,6 @@ def test_write_to_output(self): self.heat_exchanger.write_to_output() # Assert - self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_POWER_PRIMARY], 10.0) + self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_POWER_PRIMARY], -10.0) self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_LOSS], 5.0) self.assertEqual(self.heat_exchanger.outputs[0][-1][PROPERTY_HEAT_POWER_SECONDARY], 5.0) diff --git a/unit_test/entities/test_heat_pump.py b/unit_test/entities/test_heat_pump.py index c491964f..e7d9f852 100644 --- a/unit_test/entities/test_heat_pump.py +++ b/unit_test/entities/test_heat_pump.py @@ -63,6 +63,11 @@ def setUp(self) -> None: property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ] = 2.0 + self.heat_pump.solver_asset.prev_sol[ + self.heat_pump.solver_asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=1, use_relative_indexing=False + ) + ] = -2.0 self.heat_pump.solver_asset.prev_sol[ self.heat_pump.solver_asset.get_index_matrix( @@ -80,6 +85,11 @@ def setUp(self) -> None: property_name="mass_flow_rate", connection_point=2, use_relative_indexing=False ) ] = 1.0 + self.heat_pump.solver_asset.prev_sol[ + self.heat_pump.solver_asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=3, use_relative_indexing=False + ) + ] = -1.0 self.heat_pump.solver_asset.prev_sol[ self.heat_pump.solver_asset.get_index_matrix( @@ -91,27 +101,29 @@ def test_set_setpoints_secondary(self): setpoints = { SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 15.0, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 25.0, - SECONDARY + PROPERTY_HEAT_DEMAND: 310, + SECONDARY + PROPERTY_HEAT_DEMAND: -310, PROPERTY_SET_PRESSURE: True, # Boolean value } with patch( "omotes_simulator_core.entities.assets.heat_pump." "heat_demand_and_temperature_to_mass_flow", - return_value=321.0, + return_value=-321.0, ) as mock_calc: self.heat_pump._set_setpoints_secondary(setpoints) # Self attributes self.assertEqual(self.heat_pump.temperature_in_secondary, 273.15 + 15.0) self.assertEqual(self.heat_pump.temperature_out_secondary, 273.15 + 25.0) - self.assertEqual(self.heat_pump.mass_flow_secondary, 321.0) + self.assertEqual(self.heat_pump.mass_flow_secondary, -321.0) self.assertEqual(self.heat_pump.control_mass_flow_secondary, False) # Solver asset attributes self.assertEqual(self.heat_pump.solver_asset.temperature_in_secondary, 273.15 + 15.0) self.assertEqual(self.heat_pump.solver_asset.temperature_out_secondary, 273.15 + 25.0) - self.assertEqual(self.heat_pump.solver_asset.mass_flow_rate_secondary, 321.0) + self.assertEqual( + self.heat_pump.solver_asset.mass_flow_rate_rate_set_point_secondary, -321.0 + ) self.assertEqual(self.heat_pump.solver_asset.pre_scribe_mass_flow_secondary, False) mock_calc.assert_called_once_with( @@ -143,6 +155,7 @@ def test_set_setpoints_primary(self): PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 10.0, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 20.0, PRIMARY + PROPERTY_HEAT_DEMAND: 300, + PROPERTY_SET_PRESSURE: False, } with patch( @@ -155,12 +168,12 @@ def test_set_setpoints_primary(self): # Self attributes self.assertEqual(self.heat_pump.temperature_in_primary, 273.15 + 10.0) self.assertEqual(self.heat_pump.temperature_out_primary, 273.15 + 20.0) - self.assertEqual(self.heat_pump.mass_flow_initialization_primary, 125) + self.assertEqual(self.heat_pump.mass_flow_initialization_primary, -125) # Solver asset attributes self.assertEqual(self.heat_pump.solver_asset.temperature_in_primary, 273.15 + 10.0) self.assertEqual(self.heat_pump.solver_asset.temperature_out_primary, 273.15 + 20.0) - self.assertEqual(self.heat_pump.solver_asset.mass_flow_initialization_primary, 125) + self.assertEqual(self.heat_pump.solver_asset.mass_flow_initialization_primary, -125) mock_calc.assert_called_once_with( thermal_demand=300, temperature_in=273.15 + 10.0, temperature_out=273.15 + 20.0 @@ -209,7 +222,7 @@ def test_set_setpoints_calls_both_primary_and_secondary(self): self.assertEqual(self.heat_pump.solver_asset.temperature_in_secondary, 280.0) self.assertEqual(self.heat_pump.solver_asset.temperature_out_secondary, 270.0) self.assertEqual(self.heat_pump.solver_asset.pre_scribe_mass_flow_secondary, True) - self.assertEqual(self.heat_pump.mass_flow_initialization_primary, 125) + self.assertEqual(self.heat_pump.mass_flow_initialization_primary, -125) self.assertEqual(self.heat_pump.mass_flow_secondary, 125) self.assertEqual(mock_calc.call_count, 2) diff --git a/unit_test/entities/test_production_cluster.py b/unit_test/entities/test_production_cluster.py index 92b40cb7..c7d2e4c7 100644 --- a/unit_test/entities/test_production_cluster.py +++ b/unit_test/entities/test_production_cluster.py @@ -284,10 +284,10 @@ def test_is_converged_pass(self): The convergence criteria is set to 0.1% of the heat demand set point. """ # Arrange - self.production_cluster.heat_demand_set_point = 100.0 + self.production_cluster.heat_demand_set_point = -100.0 def get_actual_heat_supplied(_): - return self.production_cluster.heat_demand_set_point * (1 - 0.001) + return -self.production_cluster.heat_demand_set_point * (1 - 0.001) with patch( "omotes_simulator_core.entities.assets.production_cluster." diff --git a/unit_test/integration/test_heat_transfer_asset.py b/unit_test/integration/test_heat_transfer_asset.py index 7d899cdc..01c16655 100644 --- a/unit_test/integration/test_heat_transfer_asset.py +++ b/unit_test/integration/test_heat_transfer_asset.py @@ -90,8 +90,10 @@ def test_heat_transfer_asset_primary_positive_secondary_positive_flow(self) -> N self.heat_transfer_asset.temperature_out_primary = 20 + 273.15 self.heat_transfer_asset.temperature_out_secondary = 70 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = 1.0 - 1.0 / 3.0 - self.heat_transfer_asset.mass_flow_initialization_primary = -1 + self.heat_transfer_asset.mass_flow_initialization_primary = -77.55 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -1 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -187,8 +189,10 @@ def test_heat_transfer_asset_primary_negative_secondary_positive_flow(self) -> N self.heat_transfer_asset.temperature_out_primary = 20 + 273.15 self.heat_transfer_asset.temperature_out_secondary = 70 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = 1.0 - 1.0 / 3.0 - self.heat_transfer_asset.mass_flow_initialization_primary = +1 + self.heat_transfer_asset.mass_flow_initialization_primary = 77.55 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -1 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -283,8 +287,10 @@ def test_heat_transfer_asset_primary_positive_secondary_negative_flow(self) -> N self.heat_transfer_asset.temperature_out_primary = 20 + 273.15 self.heat_transfer_asset.temperature_out_secondary = 70 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = 1.0 - 1.0 / 3.0 - self.heat_transfer_asset.mass_flow_initialization_primary = -1 + self.heat_transfer_asset.mass_flow_initialization_primary = -77.55 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = +1 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -380,7 +386,10 @@ def test_heat_transfer_asset_positive_heat_transfer_coefficient(self) -> None: self.heat_transfer_asset.temperature_out_primary = 20 + 273.15 self.heat_transfer_asset.temperature_out_secondary = 70 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = 1.0 - 1.0 / 5.0 - self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -38.76 + self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -20.0 + self.heat_transfer_asset.mass_flow_initialization_primary = -38.76 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -406,7 +415,7 @@ def test_heat_transfer_asset_positive_heat_transfer_coefficient(self) -> None: property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -93.07, + -38.76, 2, ) self.assertAlmostEqual( @@ -467,6 +476,9 @@ def test_heat_transfer_asset_heat_transfer_coefficient_of_one(self) -> None: self.heat_transfer_asset.temperature_out_secondary = 70 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = 1.0 # - 1.0 / 5.0 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -38.76 + self.heat_transfer_asset.mass_flow_initialization_primary = -38.76 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -561,8 +573,10 @@ def test_heat_transfer_asset_negative_heat_transfer_coefficient(self) -> None: self.heat_transfer_asset.temperature_out_primary = 30 + 273.15 self.heat_transfer_asset.temperature_out_secondary = 40 + 273.15 self.heat_transfer_asset.heat_transfer_coefficient = -1 * (1.0 - 1.0 / 5.0) - self.heat_transfer_asset.mass_flow_initialization_primary = -1 + self.heat_transfer_asset.mass_flow_initialization_primary = -93.07 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = -1 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 70 + 273.15 @@ -679,6 +693,8 @@ def test_heat_transfer_asset_zero_flow(self) -> None: self.heat_transfer_asset.heat_transfer_coefficient = 1.0 - 1.0 / 5.0 self.heat_transfer_asset.mass_flow_initialization_primary = 0.0 self.heat_transfer_asset.mass_flow_rate_rate_set_point_secondary = 0.0 + self.heat_transfer_asset.pre_scribe_mass_flow_primary = True + self.heat_transfer_asset.pre_scribe_mass_flow_secondary = False # Set the temperature of the demand self.demand_asset.supply_temperature = 40 + 273.15 @@ -723,7 +739,7 @@ def test_heat_transfer_asset_zero_flow(self) -> None: property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(20 + 273.15), 2, ) self.assertAlmostEqual( @@ -741,7 +757,7 @@ def test_heat_transfer_asset_zero_flow(self) -> None: property_name="internal_energy", connection_point=2, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(secondary_in).initial_temperature), + fluid_props.get_ie(70 + 273.15), 2, ) self.assertAlmostEqual( diff --git a/unit_test/solver/network/assets/test_heat_transfer_asset.py b/unit_test/solver/network/assets/test_heat_transfer_asset.py index 691cdf80..4a9f7e53 100644 --- a/unit_test/solver/network/assets/test_heat_transfer_asset.py +++ b/unit_test/solver/network/assets/test_heat_transfer_asset.py @@ -74,12 +74,12 @@ def test_get_equations_initial_conditions_prescribe_pressure_secondary( # Assert self.assertEqual(len(equations), 12) - self.assertEqual(mock_add_continuity_equation.call_count, 1) + self.assertEqual(mock_add_continuity_equation.call_count, 0) self.assertEqual(mock_get_press_to_node_equation.call_count, 4) - self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) - self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 1) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) + self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 4) + self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 0) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -127,12 +127,12 @@ def test_get_equations_initial_conditions_prescribe_mass_flow_secondary( # Assert self.assertEqual(len(equations), 12) - self.assertEqual(mock_add_continuity_equation.call_count, 1) + self.assertEqual(mock_add_continuity_equation.call_count, 0) self.assertEqual(mock_get_press_to_node_equation.call_count, 4) - self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 0) - self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 3) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) + self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) + self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 2) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -210,12 +210,12 @@ def test_get_equations_zero_flow_prescribe_pressure_secondary( # Assert self.assertEqual(len(equations), 12) - self.assertEqual(mock_add_continuity_equation.call_count, 1) + self.assertEqual(mock_add_continuity_equation.call_count, 0) self.assertEqual(mock_get_press_to_node_equation.call_count, 4) - self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) - self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 1) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) + self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 4) + self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 0) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -294,13 +294,13 @@ def test_get_equations_flow_prescribe_mass_flow_secondary( # Assert self.assertEqual(len(equations), 12) - self.assertEqual(mock_add_continuity_equation.call_count, 1) + self.assertEqual(mock_add_continuity_equation.call_count, 0) self.assertEqual(mock_get_press_to_node_equation.call_count, 4) - self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 0) - self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 3) + self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) + self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 2) self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 2) self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 2) - self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 1) + self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( mock_add_continuity_equation.call_count @@ -377,13 +377,13 @@ def test_get_equations_with_flow_prescribe_pressure_secondary( # Assert self.assertEqual(len(equations), 12) - self.assertEqual(mock_add_continuity_equation.call_count, 1) + self.assertEqual(mock_add_continuity_equation.call_count, 0) self.assertEqual(mock_get_press_to_node_equation.call_count, 4) - self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) - self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 1) + self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 4) + self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 0) self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 2) self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 2) - self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 1) + self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( mock_add_continuity_equation.call_count @@ -414,7 +414,11 @@ def test_get_heat_power_primary(self): property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ] = 1.0 - + self.asset.prev_sol[ + self.asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=1, use_relative_indexing=False + ) + ] = -1.0 # Act heat_power = self.asset.get_heat_power_primary() @@ -439,6 +443,11 @@ def test_get_heat_power_secondary(self): property_name="mass_flow_rate", connection_point=2, use_relative_indexing=False ) ] = 1.0 + self.asset.prev_sol[ + self.asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=3, use_relative_indexing=False + ) + ] = -1.0 # Act heat_power = self.asset.get_heat_power_secondary() @@ -466,7 +475,11 @@ def test_get_electric_power_consumption(self): property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ] = 2.0 - + self.asset.prev_sol[ + self.asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=1, use_relative_indexing=False + ) + ] = 2.0 # --- Secondary side self.asset.prev_sol[ self.asset.get_index_matrix( @@ -483,7 +496,11 @@ def test_get_electric_power_consumption(self): property_name="mass_flow_rate", connection_point=2, use_relative_indexing=False ) ] = 1.0 - + self.asset.prev_sol[ + self.asset.get_index_matrix( + property_name="mass_flow_rate", connection_point=3, use_relative_indexing=False + ) + ] = 1.0 # Act electric_power = self.asset.get_electric_power_consumption() From 86feca1d0361756129e7dec6b171c05aadab81f1 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 26 Feb 2026 16:27:24 +0100 Subject: [PATCH 08/19] Fxiing typing and linting issues --- .../entities/assets/ates_cluster.py | 2 +- .../controller/controller_heat_transfer.py | 5 +-- .../assets/controller/controller_network.py | 16 ++++--- .../entities/assets/heat_pump.py | 4 +- .../entities/network_controller.py | 9 ++-- .../network/assets/heat_transfer_asset.py | 42 +++++++++++++++++++ 6 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index e024335d..909cf647 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -279,7 +279,7 @@ def _init_rosim(self) -> None: } # initially charging 12 weeks with 85-35 temperature 1 MW logger.info("initializing ates with charging for 12 weeks") - for i in range(12): + for i in range(0): logger.info(f"charging ates week {i + 1}") self.set_time_step(3600 * 24 * 7) self.set_time(datetime(2023, 1, i + 1, 0, 0, 0)) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index 6f5a8d71..0e5c7db7 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -14,8 +14,6 @@ # along with this program. If not, see . """Module containing the class for a heat trasnfer asset.""" -import numpy as np - from omotes_simulator_core.entities.assets.asset_defaults import ( PRIMARY, PROPERTY_HEAT_DEMAND, @@ -46,7 +44,8 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ The supply and return temperatures are also set. :param float heat_demand: Heat demand to set. - :param bypass: When true the heat exchange is bypassed, so the heat demand is not reduced by the factor. Default is False. + :param bypass: When true the heat exchange is bypassed, so the heat demand is not + reduced by the factor. Default is False. """ if bypass: return { diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 68ddf0d3..a9588a11 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -93,8 +93,9 @@ def exists(self, identifier: str) -> bool: def get_total_heat_demand(self, time: datetime.datetime) -> float: """Method which the total heat demand at the given time corrected to the first network.""" - return sum([consumer.get_heat_demand(time) for consumer in self.consumers]) * product( - self.factor_to_first_network + return float( + sum([consumer.get_heat_demand(time) for consumer in self.consumers]) + * product(self.factor_to_first_network) ) def get_total_discharge_storage(self) -> float: @@ -104,7 +105,8 @@ def get_total_discharge_storage(self) -> float: """ return float( sum([storage.effective_max_discharge_power for storage in self.storages]) - ) * product(self.factor_to_first_network) + * product(self.factor_to_first_network) + ) def get_total_charge_storage(self) -> float: """Method to get the total storage charge of the network corrected to the first network. @@ -113,15 +115,17 @@ def get_total_charge_storage(self) -> float: """ return float( sum([storage.effective_max_charge_power for storage in self.storages]) - ) * product(self.factor_to_first_network) + * product(self.factor_to_first_network) + ) def get_total_supply(self) -> float: """Method to get the total heat supply of the network. :return float: Total heat supply of all producers. """ - return float(sum([producer.power for producer in self.producers])) * product( - self.factor_to_first_network + return float( + sum([producer.power for producer in self.producers]) + * product(self.factor_to_first_network) ) def set_supply_to_max(self, priority: int = 0) -> dict: diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 6a8d1024..bcb9fbfb 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -145,7 +145,9 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: self.solver_asset.temperature_out_secondary = ( # type: ignore self.temperature_out_secondary ) - self.solver_asset.mass_flow_rate_rate_set_point_secondary = self.mass_flow_secondary # type: ignore + self.solver_asset.mass_flow_rate_rate_set_point_secondary = ( + self.mass_flow_secondary + ) # type: ignore self.solver_asset.pre_scribe_mass_flow_secondary = ( # type: ignore self.control_mass_flow_secondary ) diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index 71d833ff..850b3535 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -100,7 +100,8 @@ def update_setpoints(self, time: datetime.datetime) -> dict: [network.get_total_charge_storage() for network in self.networks] ) if total_charge_storage > surplus_supply: - # there is more charge capacity than surplus supply, so we can set source to max and storages to charge with the surplus supply. + # there is more charge capacity than surplus supply, so we can set source to + # max and storages to charge with the surplus supply. producer_setpoints = self._set_producers_to_max() storage_setpoints = self._set_storages_charge_power(surplus_supply) else: @@ -110,7 +111,8 @@ def update_setpoints(self, time: datetime.datetime) -> dict: total_demand + total_charge_storage ) else: - # total supply is lower than demand, so we need to check if there is enough discharge capacity from storage. + # total supply is lower than demand, so we need to check if there is enough discharge + # capacity from storage. total_discharge_storage = sum( [network.get_total_discharge_storage() for network in self.networks] ) @@ -125,7 +127,8 @@ def update_setpoints(self, time: datetime.datetime) -> dict: storage_setpoints = self._set_all_storages_discharge_to_max() consumer_setpoints = self._set_consumer_to_demand(time, factor=factor) else: - # there is enough supply + storage to cover the demand. sources to max and storages to deliver the rest. + # there is enough supply + storage to cover the demand. sources to max and + # storages to deliver the rest. consumer_setpoints = self._set_consumer_to_demand(time) surplus_demand = total_demand - total_supply producer_setpoints = self._set_producers_to_max() diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index ca678057..1ff599dc 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -215,6 +215,48 @@ def get_ordered_connection_point_list(self) -> list[int]: return [0, 1, 2, 3] def get_equations(self) -> list[EquationObject]: + r"""Return the heat transfer equations. + + The method returns the heat transfer equations for the heat transfer asset. + + The internal energy at the connection points with mass inflow are linked to the nodes. + + .. math:: + + u_{connection_point} = u_{node} + + The temperature is prescribed through the internal energy at the outlet on the + primary and secondary side of the heat transfer asset. + + .. math:: + + u_{connection_point} = u_{supply_temperature} + + The mass flow rate or pressure is prescribed at the secondary side of the heat transfer + asset. + + On the primary side, continuity of mass flow rate is enforced. + + .. math:: + + \dot{m}_{0} + \dot{m}_{1} = 0 + + If the mass flow at the inflow node of the primary and secondary side is not zero, we + prescribe the follwoing energy balance equation for the heat transfer asset: + + .. math:: + + \dot{m}_0 \left{ u_0 - u_1 \right} + C \left{ u_2 \dot{m}_2 + u_3 \dot{m}_3 \right} = 0 + + If the mass flow at the inflow node of the primary and secondary side is zero, we prescribe + the mass flow rate at the primary side of the heat transfer asset. + + .. math:: + + \dot{m}_{asset} = 10.0 + + :return: List[EquationObject] + """ equations = [] # pressure to node equations for connection_point in range(4): From 1341a825f1e07ea3765f90114090890d9d38915b Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 5 Mar 2026 16:57:47 +0100 Subject: [PATCH 09/19] Added bypass option to controller output. Added bypass mode equations in heattransfer solver asset. --- .../entities/assets/asset_abstract.py | 2 - .../entities/assets/asset_defaults.py | 1 + .../entities/assets/ates_cluster.py | 27 ++- .../controller/controller_heat_transfer.py | 7 +- .../entities/assets/demand_cluster.py | 4 +- .../entities/assets/heat_pump.py | 21 +- .../network/assets/heat_transfer_asset.py | 194 +++++++++++------- 7 files changed, 170 insertions(+), 86 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/asset_abstract.py b/src/omotes_simulator_core/entities/assets/asset_abstract.py index 3378041f..023af64a 100644 --- a/src/omotes_simulator_core/entities/assets/asset_abstract.py +++ b/src/omotes_simulator_core/entities/assets/asset_abstract.py @@ -45,8 +45,6 @@ class AssetAbstract(ABC): connected_ports: list[str] """List of ids of the connected ports.""" - solver_asset: BaseAsset - """The asset object use for the solver.""" asset_type = "asset_abstract" """The type of the asset.""" number_of_con_points: int = 2 diff --git a/src/omotes_simulator_core/entities/assets/asset_defaults.py b/src/omotes_simulator_core/entities/assets/asset_defaults.py index d5b2eb91..503fe5c4 100644 --- a/src/omotes_simulator_core/entities/assets/asset_defaults.py +++ b/src/omotes_simulator_core/entities/assets/asset_defaults.py @@ -130,6 +130,7 @@ class HeatBufferDefaults: PROPERTY_VELOCITY_SUPPLY = "velocity_supply" PROPERTY_VELOCITY_RETURN = "velocity_return" PROPERTY_SET_PRESSURE = "set_pressure" +PROPERTY_BYPASS = "bypass" PROPERTY_LENGTH = "length" PROPERTY_DIAMETER = "diameter" PROPERTY_ROUGHNESS = "roughness" diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 909cf647..390440df 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -158,9 +158,7 @@ def set_setpoints(self, setpoints: dict) -> None: :param Dict setpoints: The setpoints that should be set for the asset. The keys of the dictionary are the names of the setpoints and the values are the values """ - if self.current_time == self.time: - return - self.current_time = self.time + # Default keys required necessary_setpoints = { PROPERTY_TEMPERATURE_IN, @@ -186,8 +184,11 @@ def set_setpoints(self, setpoints: dict) -> None: self.temperature_out = self.cold_well_temperature self._calculate_massflowrate() - self._run_rosim() + if self.current_time != self.time: + self._run_rosim() + self.current_time = self.time self._set_solver_asset_setpoint() + else: # Print missing setpoints logger.error( @@ -315,3 +316,21 @@ def _run_rosim(self) -> None: self.hot_well_temperature = celcius_to_kelvin(ates_temperature[0]) # convert to K self.cold_well_temperature = celcius_to_kelvin(ates_temperature[1]) # convert to K + + def get_heat_supplied(self) -> float: + """Get the actual heat supplied by the asset. + + :return float: The actual heat supplied by the asset [W]. + """ + return ( + self.solver_asset.get_internal_energy(1) - self.solver_asset.get_internal_energy(0) + ) * self.solver_asset.get_mass_flow_rate(0) + + def is_converged(self) -> bool: + """Check if the asset has converged with accepted error of 0.1%. + + :return: True if the asset has converged, False otherwise + """ + return abs(self.get_heat_supplied() + self.thermal_power_allocation) < ( + self.thermal_power_allocation * 0.001 + ) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index 0e5c7db7..46d9747d 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -20,6 +20,7 @@ PROPERTY_SET_PRESSURE, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, + PROPERTY_BYPASS, SECONDARY, ) from omotes_simulator_core.entities.assets.controller.asset_controller_abstract import ( @@ -51,12 +52,13 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ return { self.id: { PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, - PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 50, - PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 80, + PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, + PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * -1, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, PROPERTY_SET_PRESSURE: False, + PROPERTY_BYPASS: True, } } else: @@ -69,5 +71,6 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, PROPERTY_SET_PRESSURE: False, + PROPERTY_BYPASS: False, } } diff --git a/src/omotes_simulator_core/entities/assets/demand_cluster.py b/src/omotes_simulator_core/entities/assets/demand_cluster.py index 94a9ae0b..84a8078d 100644 --- a/src/omotes_simulator_core/entities/assets/demand_cluster.py +++ b/src/omotes_simulator_core/entities/assets/demand_cluster.py @@ -134,6 +134,6 @@ def is_converged(self) -> bool: :return: True if the asset has converged, False otherwise """ - return abs(self.get_heat_supplied() - (-self.thermal_power_allocation)) < ( - (-self.thermal_power_allocation) * 0.001 + return abs(self.get_heat_supplied() - self.thermal_power_allocation) < ( + self.thermal_power_allocation * 0.001 ) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index bcb9fbfb..373d6bf7 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -29,6 +29,7 @@ PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, SECONDARY, + PROPERTY_BYPASS, ) from omotes_simulator_core.entities.assets.utils import heat_demand_and_temperature_to_mass_flow from omotes_simulator_core.solver.network.assets.heat_transfer_asset import HeatTransferAsset @@ -101,6 +102,7 @@ def __init__( pressure_set_point_secondary=DEFAULT_PRESSURE, heat_transfer_coefficient=self.coefficient_of_performance, ) + self.first_time_step = True def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: """The secondary side of the heat pump acts as a producer of heat. @@ -117,6 +119,7 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: SECONDARY + PROPERTY_TEMPERATURE_OUT, SECONDARY + PROPERTY_HEAT_DEMAND, PROPERTY_SET_PRESSURE, + PROPERTY_BYPASS, } # Dict to set setpoints_set = set(setpoints_secondary.keys()) @@ -128,10 +131,15 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: ) # Assign setpoints to the HeatPump asset - self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] + if self.first_time_step or self.solver_asset.prev_sol[0] == 0.0: + self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] + else: + self.temperature_in_secondary = self.solver_asset.get_temperature(0) + + # self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_OUT] self.mass_flow_secondary = heat_demand_and_temperature_to_mass_flow( - thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND] * -1, + thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND], temperature_in=self.temperature_in_secondary, temperature_out=self.temperature_out_secondary, ) @@ -151,6 +159,7 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: self.solver_asset.pre_scribe_mass_flow_secondary = ( # type: ignore self.control_mass_flow_secondary ) + self.solver_asset.bypass_mode = setpoints_secondary[PROPERTY_BYPASS] # type: ignore def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: """The primary side of the heat pump acts as a consumer of heat. @@ -180,7 +189,12 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: ) # Assign setpoints to the HeatPump asset - self.temperature_in_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_IN] + if self.first_time_step or self.solver_asset.prev_sol[0] == 0.0: + self.temperature_in_primary = setpoints_primary[SECONDARY + PROPERTY_TEMPERATURE_IN] + else: + self.temperature_in_primary = self.solver_asset.get_temperature(0) + + # self.temperature_in_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_OUT] self.mass_flow_initialization_primary = -heat_demand_and_temperature_to_mass_flow( thermal_demand=setpoints_primary[PRIMARY + PROPERTY_HEAT_DEMAND], @@ -212,6 +226,7 @@ def set_setpoints(self, setpoints: Dict) -> None: self._set_setpoints_primary(setpoints_primary=setpoints) # Set the setpoints for the secondary side of the heat pump self._set_setpoints_secondary(setpoints_secondary=setpoints) + self.first_time_step = False def write_to_output(self) -> None: """Get output power and electricity consumption of the asset. diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index 1ff599dc..d01eefcf 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -116,6 +116,7 @@ def __init__( self.secondary_side_inflow, self.secondary_side_outflow, ) = self.get_ordered_connection_point_list() + self.bypass_mode = False def flow_direction(self, mass_flow: float) -> FlowDirection: """Returns the flow direction of the heat transfer asset. @@ -263,78 +264,10 @@ def get_equations(self) -> list[EquationObject]: equations.append(self.get_press_to_node_equation(connection_point=connection_point)) # Internal energy to node equations - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=0, - use_relative_indexing=True, - ) - ] - < 0.0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) + if not self.bypass_mode: + self.set_internal_energy_equations(equations) else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=0, - supply_temperature=self.temperature_out_primary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=1, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=1)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=1, - supply_temperature=self.temperature_out_primary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=2, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=2)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=2, - supply_temperature=self.temperature_out_secondary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=3, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=3, - supply_temperature=self.temperature_out_secondary, - ) - ) + self.set_internal_energy_equations_bypass(equations) # set mass flow rate or pressure if self.pre_scribe_mass_flow_secondary: @@ -342,13 +275,13 @@ def get_equations(self) -> list[EquationObject]: equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=2, - mass_flow_value=-mset, + mass_flow_value=mset, ) ) equations.append( self.prescribe_mass_flow_at_connection_point( connection_point=3, - mass_flow_value=mset, + mass_flow_value=-mset, ) ) else: @@ -414,6 +347,121 @@ def get_equations(self) -> list[EquationObject]: ) return equations + def set_internal_energy_equations(self, equations): + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=0, + use_relative_indexing=True, + ) + ] + < 0.0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=0, + supply_temperature=self.temperature_out_primary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=1, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=1)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=1, + supply_temperature=self.temperature_out_primary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=2, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=2)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=2, + supply_temperature=self.temperature_out_secondary, + ) + ) + if ( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=3, + use_relative_indexing=True, + ) + ] + < 0 + ): + equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) + else: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=3, + supply_temperature=self.temperature_out_secondary, + ) + ) + + def set_internal_energy_equations_bypass(self, equations): + equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) + equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) + equation_object = EquationObject() + # Short-circuiting the primary and secondary side of the heat transfer asset. + equation_object.indices = np.array( + [ + self.get_index_matrix( + property_name="internal_energy", + connection_point=1, + use_relative_indexing=False, + ), + self.get_index_matrix( + property_name="internal_energy", + connection_point=3, + use_relative_indexing=False, + ), + ] + ) + equation_object.coefficients = np.array([1.0, -1.0]) + equation_object.rhs = 0.0 + equations.append(equation_object) + equation_object2 = EquationObject() + equation_object2.indices = np.array( + [ + self.get_index_matrix( + property_name="internal_energy", + connection_point=0, + use_relative_indexing=False, + ), + self.get_index_matrix( + property_name="internal_energy", + connection_point=2, + use_relative_indexing=False, + ), + ] + ) + equation_object2.coefficients = np.array([1.0, -1.0]) + equation_object2.rhs = 0.0 + equations.append(equation_object2) + def get_equations_old(self) -> list[EquationObject]: r"""Return the heat transfer equations. From 4c11a17753201150de20dfd9a6fa1c506d9e0bee Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Thu, 12 Mar 2026 08:35:14 +0100 Subject: [PATCH 10/19] Fixed issues, now have mode for normal and bypass operation --- .../entities/assets/ates_cluster.py | 24 +- .../controller/controller_heat_transfer.py | 14 +- .../assets/controller/controller_network.py | 12 +- .../entities/assets/heat_pump.py | 13 +- .../entities/assets/utils.py | 5 +- .../entities/network_controller.py | 7 +- .../infrastructure/app.py | 9 +- .../network/assets/heat_transfer_asset.py | 524 ++++++++---------- 8 files changed, 270 insertions(+), 338 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 390440df..80d31553 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -147,9 +147,9 @@ def _calculate_massflowrate(self) -> None: def _set_solver_asset_setpoint(self) -> None: """Set the setpoint of solver asset.""" if self.mass_flowrate >= 0: - self.solver_asset.supply_temperature = self.cold_well_temperature # injection - else: self.solver_asset.supply_temperature = self.hot_well_temperature # production + else: + self.solver_asset.supply_temperature = self.cold_well_temperature # injection self.solver_asset.mass_flow_rate_set_point = self.mass_flowrate # type: ignore def set_setpoints(self, setpoints: dict) -> None: @@ -169,19 +169,23 @@ def set_setpoints(self, setpoints: dict) -> None: setpoints_set = set(setpoints.keys()) # Check if all setpoints are in the setpoints if necessary_setpoints.issubset(setpoints_set): - self.thermal_power_allocation = -1 * setpoints[PROPERTY_HEAT_DEMAND] + self.thermal_power_allocation = -setpoints[PROPERTY_HEAT_DEMAND] if self.first_time_step: - self.temperature_in = setpoints[PROPERTY_TEMPERATURE_IN] - self.temperature_out = setpoints[PROPERTY_TEMPERATURE_OUT] + if self.thermal_power_allocation >= 0: + self.temperature_in = setpoints[PROPERTY_TEMPERATURE_OUT] + self.temperature_out = setpoints[PROPERTY_TEMPERATURE_IN] + else: + self.temperature_in = setpoints[PROPERTY_TEMPERATURE_IN] + self.temperature_out = setpoints[PROPERTY_TEMPERATURE_OUT] self.first_time_step = False else: # After the first time step: use solver temperature if self.thermal_power_allocation >= 0: + self.temperature_out = self.solver_asset.get_temperature(0) self.temperature_in = self.hot_well_temperature - self.temperature_out = self.solver_asset.get_temperature(1) else: - self.temperature_in = self.solver_asset.get_temperature(0) - self.temperature_out = self.cold_well_temperature + self.temperature_in = self.cold_well_temperature + self.temperature_out = self.solver_asset.get_temperature(1) self._calculate_massflowrate() if self.current_time != self.time: @@ -331,6 +335,6 @@ def is_converged(self) -> bool: :return: True if the asset has converged, False otherwise """ - return abs(self.get_heat_supplied() + self.thermal_power_allocation) < ( - self.thermal_power_allocation * 0.001 + return abs(self.get_heat_supplied() - self.thermal_power_allocation) < ( + abs(self.thermal_power_allocation) * 0.001 ) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index 46d9747d..acc83587 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -57,20 +57,22 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * -1, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, - PROPERTY_SET_PRESSURE: False, + SECONDARY + PROPERTY_SET_PRESSURE: False, + PRIMARY + PROPERTY_SET_PRESSURE: False, PROPERTY_BYPASS: True, } } else: return { self.id: { - PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, + PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand / self.factor, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 30, - PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, - SECONDARY + PROPERTY_HEAT_DEMAND: -heat_demand * self.factor, + PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, + SECONDARY + PROPERTY_HEAT_DEMAND: -heat_demand, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, - SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, - PROPERTY_SET_PRESSURE: False, + SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, + SECONDARY + PROPERTY_SET_PRESSURE: False, + PRIMARY + PROPERTY_SET_PRESSURE: False, PROPERTY_BYPASS: False, } } diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index a9588a11..ce1fb017 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -23,6 +23,8 @@ PROPERTY_SET_PRESSURE, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, + SECONDARY, + PRIMARY, ) from omotes_simulator_core.entities.assets.controller.controller_consumer import ControllerConsumer from omotes_simulator_core.entities.assets.controller.controller_heat_transfer import ( @@ -232,17 +234,17 @@ def get_total_supply_priority(self, priority: int) -> float: sum([producer.power for producer in self.producers if producer.priority == priority]) ) - def set_pressure(self) -> str: - """Returns the id of the asset for which the pressure can be set for this network. + def set_pressure(self) -> tuple[str, str]: + """Returns the id of the asset for which the pressure can be set for this network and the key in the set points dict. The controller needs to set per hydraulic separated part of the system the pressure. The network can thus pass back the id for which asset the pressure needs to be set. The controller can then do this. """ if self.producers: - return self.producers[0].id + return (self.producers[0].id, PROPERTY_SET_PRESSURE) if self.heat_transfer_assets_sec: - return self.heat_transfer_assets_sec[0].id + return (self.heat_transfer_assets_sec[0].id, SECONDARY + PROPERTY_SET_PRESSURE) if self.heat_transfer_assets_prim: - return self.heat_transfer_assets_prim[0].id + return (self.heat_transfer_assets_prim[0].id, PRIMARY + PROPERTY_SET_PRESSURE) raise ValueError("No asset found for which the pressure can be set.") diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 373d6bf7..6b266152 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -118,7 +118,7 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: SECONDARY + PROPERTY_TEMPERATURE_IN, SECONDARY + PROPERTY_TEMPERATURE_OUT, SECONDARY + PROPERTY_HEAT_DEMAND, - PROPERTY_SET_PRESSURE, + SECONDARY + PROPERTY_SET_PRESSURE, PROPERTY_BYPASS, } # Dict to set @@ -138,14 +138,13 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: # self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_OUT] - self.mass_flow_secondary = heat_demand_and_temperature_to_mass_flow( + self.mass_flow_secondary = -heat_demand_and_temperature_to_mass_flow( thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND], temperature_in=self.temperature_in_secondary, temperature_out=self.temperature_out_secondary, ) self.control_mass_flow_secondary = not ( - setpoints_secondary[PROPERTY_SET_PRESSURE] - & (setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND] < 0) + setpoints_secondary[SECONDARY + PROPERTY_SET_PRESSURE] ) # Assign setpoints to the HeatTransferAsset solver asset @@ -178,6 +177,7 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: PRIMARY + PROPERTY_TEMPERATURE_IN, PRIMARY + PROPERTY_TEMPERATURE_OUT, PRIMARY + PROPERTY_HEAT_DEMAND, + PRIMARY + PROPERTY_SET_PRESSURE, } # Dict to set setpoints_set = set(setpoints_primary.keys()) @@ -201,10 +201,7 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: temperature_in=self.temperature_in_primary, temperature_out=self.temperature_out_primary, ) - self.control_mass_flow_primary = not ( - setpoints_primary[PROPERTY_SET_PRESSURE] - & (setpoints_primary[PRIMARY + PROPERTY_HEAT_DEMAND] < 0) - ) + self.control_mass_flow_primary = not (setpoints_primary[PRIMARY + PROPERTY_SET_PRESSURE]) # Assign setpoints to the HeatTransferAsset solver asset self.solver_asset.temperature_in_primary = self.temperature_in_primary # type: ignore diff --git a/src/omotes_simulator_core/entities/assets/utils.py b/src/omotes_simulator_core/entities/assets/utils.py index be434324..128c72e8 100644 --- a/src/omotes_simulator_core/entities/assets/utils.py +++ b/src/omotes_simulator_core/entities/assets/utils.py @@ -35,8 +35,9 @@ def heat_demand_and_temperature_to_mass_flow( :param float temperature_in: The temperature that the asset receives from the "from_junction". The temperature should be supplied in Kelvin. """ - heat_capacity = fluid_props.get_heat_capacity((temperature_in + temperature_out) / 2) - return thermal_demand / ((temperature_out - temperature_in) * float(heat_capacity)) + internal_energy1 = fluid_props.get_ie(temperature_in) + internal_energy2 = fluid_props.get_ie(temperature_out) + return thermal_demand / (internal_energy2 - internal_energy1) def mass_flow_and_temperature_to_heat_demand( diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index 850b3535..e0e90c1e 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -60,13 +60,12 @@ def update_networks_factor(self) -> None: for asset in current_network.heat_transfer_assets_prim: if self.networks[int(step)].exists(asset.id): network.factor_to_first_network.append(asset.factor) - current_network = self.networks[int(step)] break for asset in current_network.heat_transfer_assets_sec: if self.networks[int(step)].exists(asset.id): network.factor_to_first_network.append(1 / asset.factor) - current_network = self.networks[int(step)] break + current_network = self.networks[int(step)] def update_setpoints(self, time: datetime.datetime) -> dict: """Method to get the controller inputs for the network. @@ -181,8 +180,8 @@ def update_setpoints(self, time: datetime.datetime) -> dict: # Set the pressure. for network in self.networks: - pressure_set_asset = network.set_pressure() - asset_setpoints[pressure_set_asset][PROPERTY_SET_PRESSURE] = True + pressure_set_asset, key = network.set_pressure() + asset_setpoints[pressure_set_asset][key] = True return asset_setpoints diff --git a/src/omotes_simulator_core/infrastructure/app.py b/src/omotes_simulator_core/infrastructure/app.py index a7f9d91e..936e98e1 100644 --- a/src/omotes_simulator_core/infrastructure/app.py +++ b/src/omotes_simulator_core/infrastructure/app.py @@ -41,9 +41,9 @@ def run(file_path: str | None = None) -> pd.DataFrame: config = SimulationConfiguration( simulation_id=uuid.uuid1(), name="test run", - timestep=3600, - start=datetime.strptime("2019-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S"), - stop=datetime.strptime("2019-01-01T01:00:00", "%Y-%m-%dT%H:%M:%S"), + timestep=24 * 3600, + start=datetime.strptime("2019-01-18T00:00:00", "%Y-%m-%dT%H:%M:%S"), + stop=datetime.strptime("2019-02-01T00:00:00", "%Y-%m-%dT%H:%M:%S"), ) esdl_file_path = sys.argv[1] if file_path is None else file_path @@ -63,9 +63,10 @@ def run(file_path: str | None = None) -> pd.DataFrame: level=logging.INFO, format="%(asctime)s [%(levelname)s]:%(name)s - %(message)s" ) t1 = datetime.now() - result = run(r".\testdata\test1.esdl") + result = run(r".\testdata\heat_pump_bypass.esdl") t2 = datetime.now() logger.info(f"Results dataframe shape=({result.shape})") + result.to_csv(r".\testdata\heat_pump_bypass_results.csv", index=False) logger.info(f"Execution time: {t2 - t1}") logger.debug(result.head()) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index d01eefcf..df27920a 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -44,7 +44,6 @@ def __init__( mass_flow_initialization_primary: float = -20.0, heat_transfer_coefficient: float = 1.0, pre_scribe_mass_flow_secondary: bool = False, - pre_scribe_mass_flow_primary: bool = False, temperature_out_secondary: float = 293.15, mass_flow_rate_set_point_secondary: float = -80.0, pressure_set_point_secondary: float = 10000.0, @@ -96,8 +95,8 @@ def __init__( # at the hot side of the heat pump self.pre_scribe_mass_flow_secondary = pre_scribe_mass_flow_secondary # Define the flag that indicates whether the mass flow rate or the pressure is prescribed - # at the cold side of the heat pump - self.pre_scribe_mass_flow_primary = pre_scribe_mass_flow_primary + # at the hot side of the heat pump + self.pre_scribe_mass_flow_primary = pre_scribe_mass_flow_secondary # Define the mass flow rate set point for the asset on the secondary side self.mass_flow_rate_rate_set_point_secondary = mass_flow_rate_set_point_secondary # Define the pressure set point for the asset @@ -109,6 +108,7 @@ def __init__( self.mass_flow_rate_rate_set_point_secondary ) self.iteration_flow_direction_secondary = self.flow_direction_secondary + # Define connection points ( self.primary_side_inflow, @@ -131,9 +131,9 @@ def flow_direction(self, mass_flow: float) -> FlowDirection: The flow direction of the heat transfer asset. """ if mass_flow > MASSFLOW_ZERO_LIMIT: - return FlowDirection.POSITIVE - elif mass_flow < -MASSFLOW_ZERO_LIMIT: return FlowDirection.NEGATIVE + elif mass_flow < -MASSFLOW_ZERO_LIMIT: + return FlowDirection.POSITIVE else: return FlowDirection.ZERO @@ -168,54 +168,60 @@ def get_ordered_connection_point_list(self) -> list[int]: """ # Determine the connection points based on the flow direction if ( - self.iteration_flow_direction_primary == FlowDirection.NEGATIVE - and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE + self.flow_direction_primary == FlowDirection.NEGATIVE + and self.flow_direction_secondary == FlowDirection.POSITIVE ): return [1, 0, 2, 3] elif ( - self.iteration_flow_direction_primary == FlowDirection.POSITIVE - and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE + self.flow_direction_primary == FlowDirection.POSITIVE + and self.flow_direction_secondary == FlowDirection.POSITIVE ): return [0, 1, 2, 3] elif ( - self.iteration_flow_direction_primary == FlowDirection.POSITIVE - and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE + self.flow_direction_primary == FlowDirection.POSITIVE + and self.flow_direction_secondary == FlowDirection.NEGATIVE ): return [0, 1, 3, 2] elif ( - self.iteration_flow_direction_primary == FlowDirection.NEGATIVE - and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE + self.flow_direction_primary == FlowDirection.NEGATIVE + and self.flow_direction_secondary == FlowDirection.NEGATIVE ): return [1, 0, 3, 2] elif ( - self.iteration_flow_direction_primary == FlowDirection.ZERO - and self.iteration_flow_direction_secondary == FlowDirection.ZERO + self.flow_direction_primary == FlowDirection.ZERO + and self.flow_direction_secondary == FlowDirection.ZERO ): return [0, 1, 2, 3] elif ( - self.iteration_flow_direction_primary == FlowDirection.ZERO - and self.iteration_flow_direction_secondary == FlowDirection.POSITIVE + self.flow_direction_primary == FlowDirection.ZERO + and self.flow_direction_secondary == FlowDirection.POSITIVE ): return [0, 1, 2, 3] elif ( - self.iteration_flow_direction_primary == FlowDirection.ZERO - and self.iteration_flow_direction_secondary == FlowDirection.NEGATIVE + self.flow_direction_primary == FlowDirection.ZERO + and self.flow_direction_secondary == FlowDirection.NEGATIVE ): return [0, 1, 3, 2] elif ( - self.iteration_flow_direction_primary == FlowDirection.POSITIVE - and self.iteration_flow_direction_secondary == FlowDirection.ZERO + self.flow_direction_primary == FlowDirection.POSITIVE + and self.flow_direction_secondary == FlowDirection.ZERO ): return [0, 1, 2, 3] elif ( - self.iteration_flow_direction_primary == FlowDirection.NEGATIVE - and self.iteration_flow_direction_secondary == FlowDirection.ZERO + self.flow_direction_primary == FlowDirection.NEGATIVE + and self.flow_direction_secondary == FlowDirection.ZERO ): return [1, 0, 2, 3] else: return [0, 1, 2, 3] def get_equations(self) -> list[EquationObject]: + + if self.bypass_mode: + return self.get_equations_bypass() + return self.get_equations_normal() + + def get_equations_normal(self) -> list[EquationObject]: r"""Return the heat transfer equations. The method returns the heat transfer equations for the heat transfer asset. @@ -258,30 +264,101 @@ def get_equations(self) -> list[EquationObject]: :return: List[EquationObject] """ - equations = [] - # pressure to node equations - for connection_point in range(4): - equations.append(self.get_press_to_node_equation(connection_point=connection_point)) + # Check if there are four nodes connected to the asset + if len(self.connected_nodes) != 4: + raise ValueError("The number of connected nodes must be 4!") + # Check if the number of unknowns is 12 + if self.number_of_unknowns != 12: + raise ValueError("The number of unknowns must be 12!") + # Set connection points based on the flow direction + self.flow_direction_primary = self.flow_direction(self.prev_sol[0]) + self.flow_direction_secondary = self.flow_direction(self.prev_sol[6]) + ( + self.primary_side_inflow, + self.primary_side_outflow, + self.secondary_side_inflow, + self.secondary_side_outflow, + ) = self.get_ordered_connection_point_list() - # Internal energy to node equations - if not self.bypass_mode: - self.set_internal_energy_equations(equations) + if np.all(np.abs(self.prev_sol[0:-1:3]) < MASSFLOW_ZERO_LIMIT): + self.iteration_flow_direction_primary = self.flow_direction( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=self.primary_side_inflow, + use_relative_indexing=True, + ) + ] + ) + self.iteration_flow_direction_secondary = self.flow_direction( + self.prev_sol[ + self.get_index_matrix( + property_name="mass_flow_rate", + connection_point=self.secondary_side_inflow, + use_relative_indexing=True, + ) + ] + ) else: - self.set_internal_energy_equations_bypass(equations) + self.iteration_flow_direction_primary = self.flow_direction_primary + self.iteration_flow_direction_secondary = self.flow_direction_secondary + # Initialize the equations list + equations = [] - # set mass flow rate or pressure + # -- Internal energy (4x) -- + # Add the internal energy equations at connection points 0, and 2 to define + # the connection with the nodes. + equations.append( + self.get_internal_energy_to_node_equation(connection_point=self.primary_side_inflow) + ) + equations.append( + self.get_internal_energy_to_node_equation(connection_point=self.secondary_side_inflow) + ) + # Add the internal energy equations at connection points 1, and 3 to set + # the temperature through internal energy at the outlet of the heat transfer asset. + if self.iteration_flow_direction_primary != FlowDirection.ZERO: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=self.primary_side_outflow, + supply_temperature=self.temperature_out_primary, + ) + ) + else: + equations.append( + self.get_internal_energy_to_node_equation( + connection_point=self.primary_side_outflow + ) + ) + if self.iteration_flow_direction_secondary != FlowDirection.ZERO: + equations.append( + self.prescribe_temperature_at_connection_point( + connection_point=self.secondary_side_outflow, + supply_temperature=self.temperature_out_secondary, + ) + ) + else: + equations.append( + self.get_internal_energy_to_node_equation( + connection_point=self.secondary_side_outflow + ) + ) + # -- Mass flow rate or pressure on secondary side (2x) -- + # Prescribe the pressure at the secondary side of the heat transfer asset. if self.pre_scribe_mass_flow_secondary: - mset = self.mass_flow_rate_rate_set_point_secondary + if self.iteration_flow_direction_secondary == FlowDirection.ZERO: + mset = self.mass_flow_rate_rate_set_point_secondary + else: + mset = self.mass_flow_rate_rate_set_point_secondary equations.append( self.prescribe_mass_flow_at_connection_point( - connection_point=2, - mass_flow_value=mset, + connection_point=self.secondary_side_inflow, + mass_flow_value=-mset, ) ) equations.append( self.prescribe_mass_flow_at_connection_point( - connection_point=3, - mass_flow_value=-mset, + connection_point=self.secondary_side_outflow, + mass_flow_value=mset, ) ) else: @@ -297,37 +374,63 @@ def get_equations(self) -> list[EquationObject]: pset_in = self.pressure_set_point_secondary / 2 equations.append( self.prescribe_pressure_at_connection_point( - connection_point=2, + connection_point=self.secondary_side_inflow, pressure_value=pset_in, ) ) equations.append( self.prescribe_pressure_at_connection_point( - connection_point=3, + connection_point=self.secondary_side_outflow, pressure_value=pset_out, ) ) - # set mass flow rate or pressure + # -- Pressure (4x) -- + # Connect the pressure at the nodes to the asset + for connection_point in [ + self.primary_side_inflow, + self.primary_side_outflow, + self.secondary_side_inflow, + self.secondary_side_outflow, + ]: + equations.append(self.get_press_to_node_equation(connection_point=connection_point)) + + # -- Internal continuity (1x) -- + # Add the internal continuity equation at the primary side. + + # -- Energy balance equation for the heat transfer asset (1x) -- + # Defines the energy balance between the primary and secondary side of the + # heat transfer asset. + # If the mass flow at the inflow node of the primary and secondary side is not zero, if self.pre_scribe_mass_flow_primary: - mset = self.mass_flow_initialization_primary equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=0, - mass_flow_value=mset, + self.add_continuity_equation( + connection_point_1=self.primary_side_inflow, + connection_point_2=self.primary_side_outflow, ) ) - equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=1, - mass_flow_value=mset * -1, + if (self.iteration_flow_direction_primary != FlowDirection.ZERO) or ( + self.iteration_flow_direction_secondary != FlowDirection.ZERO + ): + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=self.primary_side_inflow, + mass_flow_value=self.mass_flow_initialization_primary, + ) + ) + # If the mass flow at the inflow node of the primary and secondary side is zero, + else: + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=self.primary_side_inflow, + mass_flow_value=self.mass_flow_initialization_primary, + ) ) - ) else: - if self.iteration_flow_direction_secondary == FlowDirection.ZERO: + if self.iteration_flow_direction_primary == FlowDirection.ZERO: pset_out = self.pressure_set_point_secondary pset_in = self.pressure_set_point_secondary else: - if self.iteration_flow_direction_secondary == FlowDirection.POSITIVE: + if self.iteration_flow_direction_primary == FlowDirection.POSITIVE: pset_out = self.pressure_set_point_secondary / 2 pset_in = self.pressure_set_point_secondary else: @@ -335,134 +438,20 @@ def get_equations(self) -> list[EquationObject]: pset_in = self.pressure_set_point_secondary / 2 equations.append( self.prescribe_pressure_at_connection_point( - connection_point=0, + connection_point=self.primary_side_inflow, pressure_value=pset_in, ) ) equations.append( self.prescribe_pressure_at_connection_point( - connection_point=1, + connection_point=self.primary_side_outflow, pressure_value=pset_out, ) ) + # Return the equations return equations - def set_internal_energy_equations(self, equations): - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=0, - use_relative_indexing=True, - ) - ] - < 0.0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=0, - supply_temperature=self.temperature_out_primary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=1, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=1)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=1, - supply_temperature=self.temperature_out_primary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=2, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=2)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=2, - supply_temperature=self.temperature_out_secondary, - ) - ) - if ( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=3, - use_relative_indexing=True, - ) - ] - < 0 - ): - equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) - else: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=3, - supply_temperature=self.temperature_out_secondary, - ) - ) - - def set_internal_energy_equations_bypass(self, equations): - equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) - equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) - equation_object = EquationObject() - # Short-circuiting the primary and secondary side of the heat transfer asset. - equation_object.indices = np.array( - [ - self.get_index_matrix( - property_name="internal_energy", - connection_point=1, - use_relative_indexing=False, - ), - self.get_index_matrix( - property_name="internal_energy", - connection_point=3, - use_relative_indexing=False, - ), - ] - ) - equation_object.coefficients = np.array([1.0, -1.0]) - equation_object.rhs = 0.0 - equations.append(equation_object) - equation_object2 = EquationObject() - equation_object2.indices = np.array( - [ - self.get_index_matrix( - property_name="internal_energy", - connection_point=0, - use_relative_indexing=False, - ), - self.get_index_matrix( - property_name="internal_energy", - connection_point=2, - use_relative_indexing=False, - ), - ] - ) - equation_object2.coefficients = np.array([1.0, -1.0]) - equation_object2.rhs = 0.0 - equations.append(equation_object2) - - def get_equations_old(self) -> list[EquationObject]: + def get_equations_bypass(self) -> list[EquationObject]: r"""Return the heat transfer equations. The method returns the heat transfer equations for the heat transfer asset. @@ -505,103 +494,27 @@ def get_equations_old(self) -> list[EquationObject]: :return: List[EquationObject] """ - # Check if there are four nodes connected to the asset - if len(self.connected_nodes) != 4: - raise ValueError("The number of connected nodes must be 4!") - # Check if the number of unknowns is 12 - if self.number_of_unknowns != 12: - raise ValueError("The number of unknowns must be 12!") - # Set connection points based on the flow direction - self.flow_direction_primary = self.flow_direction(self.mass_flow_initialization_primary) - self.flow_direction_secondary = self.flow_direction( - self.mass_flow_rate_rate_set_point_secondary - ) - - if np.all(np.abs(self.prev_sol[0:-1:3]) > MASSFLOW_ZERO_LIMIT): - self.iteration_flow_direction_primary = self.flow_direction( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=self.primary_side_inflow, - use_relative_indexing=True, - ) - ] - ) - self.iteration_flow_direction_secondary = self.flow_direction( - self.prev_sol[ - self.get_index_matrix( - property_name="mass_flow_rate", - connection_point=self.secondary_side_inflow, - use_relative_indexing=True, - ) - ] - ) - else: - self.iteration_flow_direction_primary = self.flow_direction_primary - self.iteration_flow_direction_secondary = self.flow_direction_secondary - - ( - self.primary_side_inflow, - self.primary_side_outflow, - self.secondary_side_inflow, - self.secondary_side_outflow, - ) = self.get_ordered_connection_point_list() - # Initialize the equations list equations = [] + # pressure to node equations + for connection_point in range(4): + equations.append(self.get_press_to_node_equation(connection_point=connection_point)) - # -- Internal energy (4x) -- - # Add the internal energy equations at connection points 0, and 2 to define - # the connection with the nodes. - equations.append( - self.get_internal_energy_to_node_equation(connection_point=self.primary_side_inflow) - ) - equations.append( - self.get_internal_energy_to_node_equation(connection_point=self.secondary_side_inflow) - ) - # Add the internal energy equations at connection points 1, and 3 to set - # the temperature through internal energy at the outlet of the heat transfer asset. - if self.iteration_flow_direction_primary != FlowDirection.ZERO: + # Internal energy to node equations + self.set_internal_energy_equations_bypass(equations) - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=self.primary_side_outflow, - supply_temperature=self.temperature_out_primary, - ) - ) - else: - equations.append( - self.get_internal_energy_to_node_equation( - connection_point=self.primary_side_outflow - ) - ) - if self.iteration_flow_direction_secondary != FlowDirection.ZERO: - equations.append( - self.prescribe_temperature_at_connection_point( - connection_point=self.secondary_side_outflow, - supply_temperature=self.temperature_out_secondary, - ) - ) - else: - equations.append( - self.get_internal_energy_to_node_equation( - connection_point=self.secondary_side_outflow - ) - ) - # -- Mass flow rate or pressure on secondary side (2x) -- - # Prescribe the pressure at the secondary side of the heat transfer asset. + # set mass flow rate or pressure if self.pre_scribe_mass_flow_secondary: - mset = self.mass_flow_rate_rate_set_point_secondary equations.append( self.prescribe_mass_flow_at_connection_point( - connection_point=self.secondary_side_inflow, - mass_flow_value=mset, + connection_point=2, + mass_flow_value=-mset, ) ) equations.append( self.prescribe_mass_flow_at_connection_point( - connection_point=self.secondary_side_outflow, - mass_flow_value=mset * -1, + connection_point=3, + mass_flow_value=mset, ) ) else: @@ -617,55 +530,31 @@ def get_equations_old(self) -> list[EquationObject]: pset_in = self.pressure_set_point_secondary / 2 equations.append( self.prescribe_pressure_at_connection_point( - connection_point=self.secondary_side_inflow, + connection_point=2, pressure_value=pset_in, ) ) equations.append( self.prescribe_pressure_at_connection_point( - connection_point=self.secondary_side_outflow, + connection_point=3, pressure_value=pset_out, ) ) - # -- Pressure (4x) -- - # Connect the pressure at the nodes to the asset - for connection_point in [ - self.primary_side_inflow, - self.primary_side_outflow, - self.secondary_side_inflow, - self.secondary_side_outflow, - ]: - equations.append(self.get_press_to_node_equation(connection_point=connection_point)) + # set mass flow rate or pressure if self.pre_scribe_mass_flow_primary: - # -- Internal continuity (1x) -- - # Add the internal continuity equation at the primary side. + mset = self.mass_flow_initialization_primary equations.append( - self.add_continuity_equation( - connection_point_1=self.primary_side_inflow, - connection_point_2=self.primary_side_outflow, + self.prescribe_mass_flow_at_connection_point( + connection_point=0, + mass_flow_value=mset, ) ) - # -- Energy balance equation for the heat transfer asset (1x) -- - # Defines the energy balance between the primary and secondary side of the - # heat transfer asset. - # If the mass flow at the inflow node of the primary and secondary side is not zero, - if (self.iteration_flow_direction_primary != FlowDirection.ZERO) or ( - self.iteration_flow_direction_secondary != FlowDirection.ZERO - ): - equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=self.primary_side_inflow, - mass_flow_value=-1 * self.pre_scribe_mass_flow_primary, - ) - ) - # If the mass flow at the inflow node of the primary and secondary side is zero, - else: - equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=self.primary_side_inflow, - mass_flow_value=0, - ) + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=1, + mass_flow_value=mset * -1, ) + ) else: if self.iteration_flow_direction_secondary == FlowDirection.ZERO: pset_out = self.pressure_set_point_secondary @@ -679,19 +568,59 @@ def get_equations_old(self) -> list[EquationObject]: pset_in = self.pressure_set_point_secondary / 2 equations.append( self.prescribe_pressure_at_connection_point( - connection_point=self.primary_side_inflow, + connection_point=0, pressure_value=pset_in, ) ) equations.append( self.prescribe_pressure_at_connection_point( - connection_point=self.primary_side_outflow, + connection_point=1, pressure_value=pset_out, ) ) - # Return the equations return equations + def set_internal_energy_equations_bypass(self, equations): + equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) + equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) + equation_object = EquationObject() + # Short-circuiting the primary and secondary side of the heat transfer asset. + equation_object.indices = np.array( + [ + self.get_index_matrix( + property_name="internal_energy", + connection_point=1, + use_relative_indexing=False, + ), + self.get_index_matrix( + property_name="internal_energy", + connection_point=3, + use_relative_indexing=False, + ), + ] + ) + equation_object.coefficients = np.array([1.0, -1.0]) + equation_object.rhs = 0.0 + equations.append(equation_object) + equation_object2 = EquationObject() + equation_object2.indices = np.array( + [ + self.get_index_matrix( + property_name="internal_energy", + connection_point=0, + use_relative_indexing=False, + ), + self.get_index_matrix( + property_name="internal_energy", + connection_point=2, + use_relative_indexing=False, + ), + ] + ) + equation_object2.coefficients = np.array([1.0, -1.0]) + equation_object2.rhs = 0.0 + equations.append(equation_object2) + def get_mass_flow_from_prev_solution(self) -> float: r"""Determine the mass flow rate from the previous solution. @@ -968,12 +897,9 @@ def get_electric_power_consumption(self) -> float: """Calculate the electric power consumption of the heat transfer asset. The electric power consumption is calculated as the absolute difference between the - heat power on the primary and secondary side, divided by the heat transfer coefficient. + heat power on the primary and secondary side. :return: float The electric power consumption of the heat transfer asset. """ - return ( - abs(self.get_heat_power_primary() - self.get_heat_power_secondary()) - / self.heat_transfer_coefficient - ) + return abs(abs(self.get_heat_power_primary()) - abs(self.get_heat_power_secondary())) From c661c40c82901809c4f7fc451853e97f7734a2ef Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 10:06:22 +0100 Subject: [PATCH 11/19] Removed changes which should not have been comitted --- src/omotes_simulator_core/infrastructure/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/omotes_simulator_core/infrastructure/app.py b/src/omotes_simulator_core/infrastructure/app.py index 936e98e1..a7f9d91e 100644 --- a/src/omotes_simulator_core/infrastructure/app.py +++ b/src/omotes_simulator_core/infrastructure/app.py @@ -41,9 +41,9 @@ def run(file_path: str | None = None) -> pd.DataFrame: config = SimulationConfiguration( simulation_id=uuid.uuid1(), name="test run", - timestep=24 * 3600, - start=datetime.strptime("2019-01-18T00:00:00", "%Y-%m-%dT%H:%M:%S"), - stop=datetime.strptime("2019-02-01T00:00:00", "%Y-%m-%dT%H:%M:%S"), + timestep=3600, + start=datetime.strptime("2019-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S"), + stop=datetime.strptime("2019-01-01T01:00:00", "%Y-%m-%dT%H:%M:%S"), ) esdl_file_path = sys.argv[1] if file_path is None else file_path @@ -63,10 +63,9 @@ def run(file_path: str | None = None) -> pd.DataFrame: level=logging.INFO, format="%(asctime)s [%(levelname)s]:%(name)s - %(message)s" ) t1 = datetime.now() - result = run(r".\testdata\heat_pump_bypass.esdl") + result = run(r".\testdata\test1.esdl") t2 = datetime.now() logger.info(f"Results dataframe shape=({result.shape})") - result.to_csv(r".\testdata\heat_pump_bypass_results.csv", index=False) logger.info(f"Execution time: {t2 - t1}") logger.debug(result.head()) From 303e5db74385ffffa0d1f7b4a3f753ce9ae8cd0b Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 10:16:13 +0100 Subject: [PATCH 12/19] Fixed copilots comments --- .../entities/assets/controller/controller_network.py | 10 +++++----- .../entities/assets/demand_cluster.py | 2 +- src/omotes_simulator_core/entities/assets/heat_pump.py | 2 +- .../solver/network/assets/heat_transfer_asset.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index ce1fb017..81cf6b88 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -16,7 +16,7 @@ import datetime -from numpy.ma.core import product +import numpy as np from omotes_simulator_core.entities.assets.asset_defaults import ( PROPERTY_HEAT_DEMAND, @@ -97,7 +97,7 @@ def get_total_heat_demand(self, time: datetime.datetime) -> float: """Method which the total heat demand at the given time corrected to the first network.""" return float( sum([consumer.get_heat_demand(time) for consumer in self.consumers]) - * product(self.factor_to_first_network) + * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) ) def get_total_discharge_storage(self) -> float: @@ -107,7 +107,7 @@ def get_total_discharge_storage(self) -> float: """ return float( sum([storage.effective_max_discharge_power for storage in self.storages]) - * product(self.factor_to_first_network) + * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) ) def get_total_charge_storage(self) -> float: @@ -117,7 +117,7 @@ def get_total_charge_storage(self) -> float: """ return float( sum([storage.effective_max_charge_power for storage in self.storages]) - * product(self.factor_to_first_network) + * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) ) def get_total_supply(self) -> float: @@ -127,7 +127,7 @@ def get_total_supply(self) -> float: """ return float( sum([producer.power for producer in self.producers]) - * product(self.factor_to_first_network) + * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) ) def set_supply_to_max(self, priority: int = 0) -> dict: diff --git a/src/omotes_simulator_core/entities/assets/demand_cluster.py b/src/omotes_simulator_core/entities/assets/demand_cluster.py index 84a8078d..b0572b8b 100644 --- a/src/omotes_simulator_core/entities/assets/demand_cluster.py +++ b/src/omotes_simulator_core/entities/assets/demand_cluster.py @@ -135,5 +135,5 @@ def is_converged(self) -> bool: :return: True if the asset has converged, False otherwise """ return abs(self.get_heat_supplied() - self.thermal_power_allocation) < ( - self.thermal_power_allocation * 0.001 + abs(self.thermal_power_allocation) * 0.001 ) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 6b266152..2aa3990d 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -190,7 +190,7 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: # Assign setpoints to the HeatPump asset if self.first_time_step or self.solver_asset.prev_sol[0] == 0.0: - self.temperature_in_primary = setpoints_primary[SECONDARY + PROPERTY_TEMPERATURE_IN] + self.temperature_in_primary = setpoints_primary[PRIMARY + PROPERTY_TEMPERATURE_IN] else: self.temperature_in_primary = self.solver_asset.get_temperature(0) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index df27920a..fd2dac3c 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -479,7 +479,7 @@ def get_equations_bypass(self) -> list[EquationObject]: \dot{m}_{0} + \dot{m}_{1} = 0 If the mass flow at the inflow node of the primary and secondary side is not zero, we - prescribe the follwoing energy balance equation for the heat transfer asset: + prescribe the following energy balance equation for the heat transfer asset: .. math:: From ce7226e48915c6fb38a0c49f0e543c7093d7b409 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 14:29:17 +0100 Subject: [PATCH 13/19] Fixes based on review and fixed unit testing --- .../entities/assets/asset_abstract.py | 4 +- .../entities/assets/ates_cluster.py | 7 +++- .../controller/controller_heat_transfer.py | 4 +- .../assets/controller/controller_network.py | 8 ++-- .../entities/assets/heat_exchanger.py | 2 +- .../entities/assets/heat_pump.py | 20 +++++---- .../entities/network_controller.py | 15 +++---- .../network/assets/heat_transfer_asset.py | 41 ++++++++----------- unit_test/entities/test_heat_exchanger.py | 6 +-- unit_test/entities/test_heat_pump.py | 18 +++++--- .../integration/test_heat_transfer_asset.py | 34 +++++++-------- .../assets/test_heat_transfer_asset.py | 14 +++---- 12 files changed, 90 insertions(+), 83 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/asset_abstract.py b/src/omotes_simulator_core/entities/assets/asset_abstract.py index 023af64a..7b746630 100644 --- a/src/omotes_simulator_core/entities/assets/asset_abstract.py +++ b/src/omotes_simulator_core/entities/assets/asset_abstract.py @@ -45,6 +45,8 @@ class AssetAbstract(ABC): connected_ports: list[str] """List of ids of the connected ports.""" + solver_asset: BaseAsset + """The asset object use for the solver.""" asset_type = "asset_abstract" """The type of the asset.""" number_of_con_points: int = 2 @@ -132,7 +134,7 @@ def write_to_output(self) -> None: """ def get_timeseries(self) -> DataFrame: - """Get timeseries as a dataframe from a asset. + """Get timeseries as a dataframe from an asset. The header is a tuple of the port id and the property name. """ diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 80d31553..3ade37ea 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -169,8 +169,11 @@ def set_setpoints(self, setpoints: dict) -> None: setpoints_set = set(setpoints.keys()) # Check if all setpoints are in the setpoints if necessary_setpoints.issubset(setpoints_set): - self.thermal_power_allocation = -setpoints[PROPERTY_HEAT_DEMAND] + self.thermal_power_allocation = -1 * setpoints[PROPERTY_HEAT_DEMAND] if self.first_time_step: + # Depending on the sign of the power allocation the ATES is charging or discharging. + # If positive then charging and the Flow direction is negative, So in and out temperature + # are switch, since they are not set on flow direction but on port. if self.thermal_power_allocation >= 0: self.temperature_in = setpoints[PROPERTY_TEMPERATURE_OUT] self.temperature_out = setpoints[PROPERTY_TEMPERATURE_IN] @@ -284,7 +287,7 @@ def _init_rosim(self) -> None: } # initially charging 12 weeks with 85-35 temperature 1 MW logger.info("initializing ates with charging for 12 weeks") - for i in range(0): + for i in range(12): logger.info(f"charging ates week {i + 1}") self.set_time_step(3600 * 24 * 7) self.set_time(datetime(2023, 1, i + 1, 0, 0, 0)) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index acc83587..c9d71dfb 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -65,10 +65,10 @@ def set_asset(self, heat_demand: float, bypass: bool = False) -> dict[str, dict[ else: return { self.id: { - PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand / self.factor, + PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 30, PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, - SECONDARY + PROPERTY_HEAT_DEMAND: -heat_demand, + SECONDARY + PROPERTY_HEAT_DEMAND: heat_demand * -1 * self.factor, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 80, SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 40, SECONDARY + PROPERTY_SET_PRESSURE: False, diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 81cf6b88..6f41c25e 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -97,7 +97,7 @@ def get_total_heat_demand(self, time: datetime.datetime) -> float: """Method which the total heat demand at the given time corrected to the first network.""" return float( sum([consumer.get_heat_demand(time) for consumer in self.consumers]) - * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) + * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) ) def get_total_discharge_storage(self) -> float: @@ -107,7 +107,7 @@ def get_total_discharge_storage(self) -> float: """ return float( sum([storage.effective_max_discharge_power for storage in self.storages]) - * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) + * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) ) def get_total_charge_storage(self) -> float: @@ -117,7 +117,7 @@ def get_total_charge_storage(self) -> float: """ return float( sum([storage.effective_max_charge_power for storage in self.storages]) - * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) + * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) ) def get_total_supply(self) -> float: @@ -127,7 +127,7 @@ def get_total_supply(self) -> float: """ return float( sum([producer.power for producer in self.producers]) - * float(np.prod(np.array(self.factor_to_first_network, dtype=int))) + * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) ) def set_supply_to_max(self, priority: int = 0) -> dict: diff --git a/src/omotes_simulator_core/entities/assets/heat_exchanger.py b/src/omotes_simulator_core/entities/assets/heat_exchanger.py index a807dd73..edcb7448 100644 --- a/src/omotes_simulator_core/entities/assets/heat_exchanger.py +++ b/src/omotes_simulator_core/entities/assets/heat_exchanger.py @@ -210,7 +210,7 @@ def write_to_output(self) -> None: self.solver_asset.get_heat_power_primary() # type: ignore ), PROPERTY_HEAT_LOSS: ( - -self.solver_asset.get_heat_power_primary() # type: ignore + self.solver_asset.get_heat_power_primary() # type: ignore - self.solver_asset.get_heat_power_secondary() # type: ignore ), } diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 2aa3990d..f35663c3 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -108,10 +108,11 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: """The secondary side of the heat pump acts as a producer of heat. The necessary setpoints are: - - PROPERTY_TEMPERATURE_IN - - PROPERTY_TEMPERATURE_OUT - - PROPERTY_HEAT_DEMAND - - PROPERTY_SET_PRESSURE + - SECONDARY + PROPERTY_TEMPERATURE_IN + - SECONDARY + PROPERTY_TEMPERATURE_OUT + - SECONDARY +PROPERTY_HEAT_DEMAND + - SECONDARY + PROPERTY_SET_PRESSURE + - PROPERTY_BYPASS """ # Default keys required necessary_setpoints = { @@ -134,11 +135,11 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: if self.first_time_step or self.solver_asset.prev_sol[0] == 0.0: self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] else: - self.temperature_in_secondary = self.solver_asset.get_temperature(0) + self.temperature_in_secondary = self.solver_asset.get_temperature(2) # self.temperature_in_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_IN] self.temperature_out_secondary = setpoints_secondary[SECONDARY + PROPERTY_TEMPERATURE_OUT] - self.mass_flow_secondary = -heat_demand_and_temperature_to_mass_flow( + self.mass_flow_secondary = heat_demand_and_temperature_to_mass_flow( thermal_demand=setpoints_secondary[SECONDARY + PROPERTY_HEAT_DEMAND], temperature_in=self.temperature_in_secondary, temperature_out=self.temperature_out_secondary, @@ -164,9 +165,10 @@ def _set_setpoints_primary(self, setpoints_primary: Dict) -> None: """The primary side of the heat pump acts as a consumer of heat. The necessary setpoints are: - - PROPERTY_TEMPERATURE_IN - - PROPERTY_TEMPERATURE_OUT - - PROPERTY_HEAT_DEMAND + - PRIMARY + PROPERTY_TEMPERATURE_IN + - PRIMARY + PROPERTY_TEMPERATURE_OUT + - PRIMARY + PROPERTY_HEAT_DEMAND + - PRIMARY + PROPERTY_SET_PRESSURE :param Dict setpoints_primary: The setpoints of the primary side of the heat pump. """ diff --git a/src/omotes_simulator_core/entities/network_controller.py b/src/omotes_simulator_core/entities/network_controller.py index e0e90c1e..71528dc9 100644 --- a/src/omotes_simulator_core/entities/network_controller.py +++ b/src/omotes_simulator_core/entities/network_controller.py @@ -143,7 +143,7 @@ def update_setpoints(self, time: datetime.datetime) -> dict: # Getting the settings for the heat transfer assets heat_transfer: AssetSetpointsDict = {} - # Set all the networks where there is only on primary or secondary heat exchanger. + # Set all the networks where there is only one primary or secondary heat exchanger. # Everything will then be set, since all heat transfer assets belong to a network where # they are the only one. for network in self.networks: @@ -162,18 +162,19 @@ def update_setpoints(self, time: datetime.datetime) -> dict: for storage in network.storages: total_heat_supply -= asset_setpoints[storage.id][PROPERTY_HEAT_DEMAND] - # this might look weird, but we know there is only one primary or secondary asset. - # So we can directly set it. + # this might look weird, but we know ih this network there is only one primary + # or one secondary heat transfer asset. So if we loop over them either there are + # no items to loop over or there is only one. And that one we can set directly, for asset in network.heat_transfer_assets_prim: if total_heat_supply > 0: - heat_transfer.update(asset.set_asset(total_heat_supply)) + heat_transfer.update(asset.set_asset(total_heat_supply, bypass=False)) else: - heat_transfer.update(asset.set_asset(-total_heat_supply, True)) + heat_transfer.update(asset.set_asset(-total_heat_supply, bypass=True)) for asset in network.heat_transfer_assets_sec: if total_heat_supply > 0: - heat_transfer.update(asset.set_asset(-total_heat_supply, True)) + heat_transfer.update(asset.set_asset(-total_heat_supply, bypass=True)) else: - heat_transfer.update(asset.set_asset(-total_heat_supply)) + heat_transfer.update(asset.set_asset(-total_heat_supply, bypass=False)) # Update the asset setpoints with the heat transfer setpoints. asset_setpoints.update(heat_transfer) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index fd2dac3c..61e39887 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -47,6 +47,7 @@ def __init__( temperature_out_secondary: float = 293.15, mass_flow_rate_set_point_secondary: float = -80.0, pressure_set_point_secondary: float = 10000.0, + pre_scribe_mass_flow_primary: bool = False, ): """ Initializes the Heat Transfer Asset with the given parameters. @@ -72,6 +73,9 @@ def __init__( pre_scribe_mass_flow_secondary : bool A boolean flag that indicates whether the mass flow rate or the pressure is prescribed at the secondary side of the heat transfer asset. + pre_scribe_mass_flow_primary : bool + A boolean flag that indicates whether the mass flow rate or the pressure is prescribed + at the primary side of the heat transfer asset. mass_flow_rate_set_point_secondary : float, optional The mass flow rate set point for the asset. The default is 10.0 kg/s. pressure_set_point_secondary : float, optional @@ -96,7 +100,7 @@ def __init__( self.pre_scribe_mass_flow_secondary = pre_scribe_mass_flow_secondary # Define the flag that indicates whether the mass flow rate or the pressure is prescribed # at the hot side of the heat pump - self.pre_scribe_mass_flow_primary = pre_scribe_mass_flow_secondary + self.pre_scribe_mass_flow_primary = pre_scribe_mass_flow_primary # Define the mass flow rate set point for the asset on the secondary side self.mass_flow_rate_rate_set_point_secondary = mass_flow_rate_set_point_secondary # Define the pressure set point for the asset @@ -218,10 +222,10 @@ def get_ordered_connection_point_list(self) -> list[int]: def get_equations(self) -> list[EquationObject]: if self.bypass_mode: - return self.get_equations_bypass() - return self.get_equations_normal() + return self.get_equations_bypass_mode() + return self.get_equations_heat_transfer_mode() - def get_equations_normal(self) -> list[EquationObject]: + def get_equations_heat_transfer_mode(self) -> list[EquationObject]: r"""Return the heat transfer equations. The method returns the heat transfer equations for the heat transfer asset. @@ -403,28 +407,17 @@ def get_equations_normal(self) -> list[EquationObject]: # If the mass flow at the inflow node of the primary and secondary side is not zero, if self.pre_scribe_mass_flow_primary: equations.append( - self.add_continuity_equation( - connection_point_1=self.primary_side_inflow, - connection_point_2=self.primary_side_outflow, + self.prescribe_mass_flow_at_connection_point( + connection_point=0, + mass_flow_value=-1 * self.mass_flow_initialization_primary, ) ) - if (self.iteration_flow_direction_primary != FlowDirection.ZERO) or ( - self.iteration_flow_direction_secondary != FlowDirection.ZERO - ): - equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=self.primary_side_inflow, - mass_flow_value=self.mass_flow_initialization_primary, - ) - ) - # If the mass flow at the inflow node of the primary and secondary side is zero, - else: - equations.append( - self.prescribe_mass_flow_at_connection_point( - connection_point=self.primary_side_inflow, - mass_flow_value=self.mass_flow_initialization_primary, - ) + equations.append( + self.prescribe_mass_flow_at_connection_point( + connection_point=1, + mass_flow_value=self.mass_flow_initialization_primary, ) + ) else: if self.iteration_flow_direction_primary == FlowDirection.ZERO: pset_out = self.pressure_set_point_secondary @@ -451,7 +444,7 @@ def get_equations_normal(self) -> list[EquationObject]: # Return the equations return equations - def get_equations_bypass(self) -> list[EquationObject]: + def get_equations_bypass_mode(self) -> list[EquationObject]: r"""Return the heat transfer equations. The method returns the heat transfer equations for the heat transfer asset. diff --git a/unit_test/entities/test_heat_exchanger.py b/unit_test/entities/test_heat_exchanger.py index 0f0cba67..fd310437 100644 --- a/unit_test/entities/test_heat_exchanger.py +++ b/unit_test/entities/test_heat_exchanger.py @@ -50,7 +50,7 @@ def setUp(self) -> None: self.heat_exchanger.solver_asset.get_index_matrix( property_name="internal_energy", connection_point=0, use_relative_indexing=False ) - ] = 5.0 + ] = 10.0 self.heat_exchanger.solver_asset.prev_sol[ self.heat_exchanger.solver_asset.get_index_matrix( property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False @@ -65,7 +65,7 @@ def setUp(self) -> None: self.heat_exchanger.solver_asset.get_index_matrix( property_name="internal_energy", connection_point=1, use_relative_indexing=False ) - ] = 10.0 + ] = 5.0 self.heat_exchanger.solver_asset.prev_sol[ self.heat_exchanger.solver_asset.get_index_matrix( @@ -97,6 +97,6 @@ def test_write_to_output(self): self.heat_exchanger.write_to_output() # Assert - self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_POWER_PRIMARY], -10.0) + self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_POWER_PRIMARY], 10.0) self.assertEqual(self.heat_exchanger.outputs[1][-1][PROPERTY_HEAT_LOSS], 5.0) self.assertEqual(self.heat_exchanger.outputs[0][-1][PROPERTY_HEAT_POWER_SECONDARY], 5.0) diff --git a/unit_test/entities/test_heat_pump.py b/unit_test/entities/test_heat_pump.py index e7d9f852..077e3ef0 100644 --- a/unit_test/entities/test_heat_pump.py +++ b/unit_test/entities/test_heat_pump.py @@ -27,6 +27,7 @@ PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, SECONDARY, + PROPERTY_BYPASS, ) from omotes_simulator_core.entities.assets.heat_pump import HeatPump @@ -102,7 +103,8 @@ def test_set_setpoints_secondary(self): SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 15.0, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 25.0, SECONDARY + PROPERTY_HEAT_DEMAND: -310, - PROPERTY_SET_PRESSURE: True, # Boolean value + SECONDARY + PROPERTY_SET_PRESSURE: True, # Boolean value + PROPERTY_BYPASS: False, } with patch( @@ -127,7 +129,7 @@ def test_set_setpoints_secondary(self): self.assertEqual(self.heat_pump.solver_asset.pre_scribe_mass_flow_secondary, False) mock_calc.assert_called_once_with( - thermal_demand=310, temperature_in=273.15 + 15.0, temperature_out=273.15 + 25.0 + thermal_demand=-310, temperature_in=273.15 + 15.0, temperature_out=273.15 + 25.0 ) def test_set_setpoints_secondary_missing_key(self): @@ -136,7 +138,8 @@ def test_set_setpoints_secondary_missing_key(self): SECONDARY + PROPERTY_TEMPERATURE_IN: 273.15 + 15.0, SECONDARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 25.0, SECONDARY + PROPERTY_HEAT_DEMAND: 310, - PROPERTY_SET_PRESSURE: True, # Boolean value + SECONDARY + PROPERTY_SET_PRESSURE: True, # Boolean value + PROPERTY_BYPASS: True, } # Act & Assert @@ -155,7 +158,7 @@ def test_set_setpoints_primary(self): PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 10.0, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 20.0, PRIMARY + PROPERTY_HEAT_DEMAND: 300, - PROPERTY_SET_PRESSURE: False, + PRIMARY + PROPERTY_SET_PRESSURE: False, } with patch( @@ -184,6 +187,7 @@ def test_set_setpoints_primary_missing_key(self): PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 10.0, PRIMARY + PROPERTY_TEMPERATURE_OUT: 273.15 + 20.0, PRIMARY + PROPERTY_HEAT_DEMAND: 300, + PRIMARY + PROPERTY_SET_PRESSURE: False, } for missing_key in base_setpoints.keys(): @@ -205,7 +209,9 @@ def test_set_setpoints_calls_both_primary_and_secondary(self): SECONDARY + PROPERTY_TEMPERATURE_OUT: 270.0, PRIMARY + PROPERTY_HEAT_DEMAND: 1500, SECONDARY + PROPERTY_HEAT_DEMAND: 1000, - PROPERTY_SET_PRESSURE: False, + PRIMARY + PROPERTY_SET_PRESSURE: False, + SECONDARY + PROPERTY_SET_PRESSURE: False, + PROPERTY_BYPASS : False, } # Act @@ -236,5 +242,5 @@ def test_write_to_output(self): # Assert self.assertEqual(self.heat_pump.outputs[1][-1][PROPERTY_HEAT_POWER_PRIMARY], 10.0) - self.assertEqual(self.heat_pump.outputs[1][-1][PROPERTY_ELECTRICITY_CONSUMPTION], 1.0) + self.assertEqual(self.heat_pump.outputs[1][-1][PROPERTY_ELECTRICITY_CONSUMPTION], 5.0) self.assertEqual(self.heat_pump.outputs[0][-1][PROPERTY_HEAT_POWER_SECONDARY], 5.0) diff --git a/unit_test/integration/test_heat_transfer_asset.py b/unit_test/integration/test_heat_transfer_asset.py index 01c16655..35a4e625 100644 --- a/unit_test/integration/test_heat_transfer_asset.py +++ b/unit_test/integration/test_heat_transfer_asset.py @@ -120,7 +120,7 @@ def test_heat_transfer_asset_primary_positive_secondary_positive_flow(self) -> N property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -77.55, + 77.55, 2, ) self.assertAlmostEqual( @@ -138,7 +138,7 @@ def test_heat_transfer_asset_primary_positive_secondary_positive_flow(self) -> N property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.heat_transfer_asset.temperature_out_primary), 2, ) self.assertAlmostEqual( @@ -219,7 +219,7 @@ def test_heat_transfer_asset_primary_negative_secondary_positive_flow(self) -> N property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - 77.55, + -77.55, 2, ) self.assertAlmostEqual( @@ -237,7 +237,7 @@ def test_heat_transfer_asset_primary_negative_secondary_positive_flow(self) -> N property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.production_asset.supply_temperature), 2, ) self.assertAlmostEqual( @@ -246,7 +246,7 @@ def test_heat_transfer_asset_primary_negative_secondary_positive_flow(self) -> N property_name="internal_energy", connection_point=2, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(secondary_in).initial_temperature), + fluid_props.get_ie(self.demand_asset.supply_temperature), 2, ) @@ -317,7 +317,7 @@ def test_heat_transfer_asset_primary_positive_secondary_negative_flow(self) -> N property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -77.55, + 77.55, 2, ) self.assertAlmostEqual( @@ -335,7 +335,7 @@ def test_heat_transfer_asset_primary_positive_secondary_negative_flow(self) -> N property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.heat_transfer_asset.temperature_out_primary), 2, ) self.assertAlmostEqual( @@ -415,7 +415,7 @@ def test_heat_transfer_asset_positive_heat_transfer_coefficient(self) -> None: property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -38.76, + 38.76, 2, ) self.assertAlmostEqual( @@ -433,7 +433,7 @@ def test_heat_transfer_asset_positive_heat_transfer_coefficient(self) -> None: property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.heat_transfer_asset.temperature_out_primary), 2, ) @@ -504,7 +504,7 @@ def test_heat_transfer_asset_heat_transfer_coefficient_of_one(self) -> None: property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -38.76, + 38.76, 2, ) self.assertAlmostEqual( @@ -522,7 +522,7 @@ def test_heat_transfer_asset_heat_transfer_coefficient_of_one(self) -> None: property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.heat_transfer_asset.temperature_out_primary), 2, ) self.assertAlmostEqual( @@ -531,7 +531,7 @@ def test_heat_transfer_asset_heat_transfer_coefficient_of_one(self) -> None: property_name="internal_energy", connection_point=2, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(secondary_in).initial_temperature), + fluid_props.get_ie(self.demand_asset.supply_temperature), 2, ) @@ -603,7 +603,7 @@ def test_heat_transfer_asset_negative_heat_transfer_coefficient(self) -> None: property_name="mass_flow_rate", connection_point=0, use_relative_indexing=False ) ], - -93.07, + 93.07, 2, ) self.assertAlmostEqual( @@ -621,7 +621,7 @@ def test_heat_transfer_asset_negative_heat_transfer_coefficient(self) -> None: property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_in).initial_temperature), + fluid_props.get_ie(self.heat_transfer_asset.temperature_out_primary), 2, ) self.assertAlmostEqual( @@ -630,7 +630,7 @@ def test_heat_transfer_asset_negative_heat_transfer_coefficient(self) -> None: property_name="internal_energy", connection_point=1, use_relative_indexing=False ) ], - fluid_props.get_ie(self.network.get_node(primary_out).initial_temperature), + fluid_props.get_ie(self.production_asset.supply_temperature), 2, ) self.assertAlmostEqual( @@ -739,7 +739,7 @@ def test_heat_transfer_asset_zero_flow(self) -> None: property_name="internal_energy", connection_point=0, use_relative_indexing=False ) ], - fluid_props.get_ie(20 + 273.15), + fluid_props.get_ie(30 + 273.15), 2, ) self.assertAlmostEqual( @@ -757,7 +757,7 @@ def test_heat_transfer_asset_zero_flow(self) -> None: property_name="internal_energy", connection_point=2, use_relative_indexing=False ) ], - fluid_props.get_ie(70 + 273.15), + fluid_props.get_ie(40 + 273.15), 2, ) self.assertAlmostEqual( diff --git a/unit_test/solver/network/assets/test_heat_transfer_asset.py b/unit_test/solver/network/assets/test_heat_transfer_asset.py index 4a9f7e53..9b53b6b6 100644 --- a/unit_test/solver/network/assets/test_heat_transfer_asset.py +++ b/unit_test/solver/network/assets/test_heat_transfer_asset.py @@ -78,8 +78,8 @@ def test_get_equations_initial_conditions_prescribe_pressure_secondary( self.assertEqual(mock_get_press_to_node_equation.call_count, 4) self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 4) self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 0) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -131,8 +131,8 @@ def test_get_equations_initial_conditions_prescribe_mass_flow_secondary( self.assertEqual(mock_get_press_to_node_equation.call_count, 4) self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 2) self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 2) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -214,8 +214,8 @@ def test_get_equations_zero_flow_prescribe_pressure_secondary( self.assertEqual(mock_get_press_to_node_equation.call_count, 4) self.assertEqual(mock_prescribe_pressure_at_connection_point.call_count, 4) self.assertEqual(mock_prescribe_mass_flow_at_connection_point.call_count, 0) - self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 4) - self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 0) + self.assertEqual(mock_prescribe_temperature_at_connection_point.call_count, 0) + self.assertEqual(mock_get_internal_energy_to_node_equation.call_count, 4) self.assertEqual(mock_get_mass_flow_from_prev_solution.call_count, 0) self.assertEqual( ( @@ -505,4 +505,4 @@ def test_get_electric_power_consumption(self): electric_power = self.asset.get_electric_power_consumption() # Assert - self.assertEqual(electric_power, 1.0) + self.assertEqual(electric_power, 5.0) From 40a9cec9b48368e7f74601ff547d0a5e301e016c Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 15:23:55 +0100 Subject: [PATCH 14/19] Fixed linting erros and error in carrier setting of esdl --- .../entities/assets/ates_cluster.py | 5 ++--- .../assets/controller/controller_network.py | 2 +- .../entities/assets/heat_exchanger.py | 17 ++++++++--------- .../network/assets/heat_transfer_asset.py | 11 +++++++++-- testdata/heat_transfers_test.esdl | 18 +++++++++--------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 3ade37ea..e0ac046b 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -158,7 +158,6 @@ def set_setpoints(self, setpoints: dict) -> None: :param Dict setpoints: The setpoints that should be set for the asset. The keys of the dictionary are the names of the setpoints and the values are the values """ - # Default keys required necessary_setpoints = { PROPERTY_TEMPERATURE_IN, @@ -172,8 +171,8 @@ def set_setpoints(self, setpoints: dict) -> None: self.thermal_power_allocation = -1 * setpoints[PROPERTY_HEAT_DEMAND] if self.first_time_step: # Depending on the sign of the power allocation the ATES is charging or discharging. - # If positive then charging and the Flow direction is negative, So in and out temperature - # are switch, since they are not set on flow direction but on port. + # If positive then charging and the Flow direction is negative, So in and out + # temperature are switch, since they are not set on flow direction but on port. if self.thermal_power_allocation >= 0: self.temperature_in = setpoints[PROPERTY_TEMPERATURE_OUT] self.temperature_out = setpoints[PROPERTY_TEMPERATURE_IN] diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 6f41c25e..44de550c 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -235,7 +235,7 @@ def get_total_supply_priority(self, priority: int) -> float: ) def set_pressure(self) -> tuple[str, str]: - """Returns the id of the asset for which the pressure can be set for this network and the key in the set points dict. + """Returns the id of the asset for which the pressure can be set and the key in the set points dict. The controller needs to set per hydraulic separated part of the system the pressure. The network can thus pass back the id for which asset the pressure needs to be set. diff --git a/src/omotes_simulator_core/entities/assets/heat_exchanger.py b/src/omotes_simulator_core/entities/assets/heat_exchanger.py index edcb7448..956389ab 100644 --- a/src/omotes_simulator_core/entities/assets/heat_exchanger.py +++ b/src/omotes_simulator_core/entities/assets/heat_exchanger.py @@ -66,11 +66,11 @@ class HeatExchanger(AssetAbstract): """Heat transfer efficiency of the heat exchanger [W/K].""" def __init__( - self, - asset_name: str, - asset_id: str, - connected_ports: list[str], - heat_transfer_efficiency: float = 1, + self, + asset_name: str, + asset_id: str, + connected_ports: list[str], + heat_transfer_efficiency: float = 1, ) -> None: """Initialize a new HeatExchanger instance. @@ -209,10 +209,9 @@ def write_to_output(self) -> None: PROPERTY_HEAT_POWER_PRIMARY: ( self.solver_asset.get_heat_power_primary() # type: ignore ), - PROPERTY_HEAT_LOSS: ( - self.solver_asset.get_heat_power_primary() # type: ignore - - self.solver_asset.get_heat_power_secondary() # type: ignore - ), + PROPERTY_HEAT_LOSS: (self.solver_asset.get_heat_power_primary() # type: ignore + - self.solver_asset.get_heat_power_secondary() # type: ignore + ), } ) diff --git a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py index 61e39887..fb4c75a8 100644 --- a/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py +++ b/src/omotes_simulator_core/solver/network/assets/heat_transfer_asset.py @@ -220,7 +220,7 @@ def get_ordered_connection_point_list(self) -> list[int]: return [0, 1, 2, 3] def get_equations(self) -> list[EquationObject]: - + r"""Returns the list of equations to be solved for the heat transfer asset.""" if self.bypass_mode: return self.get_equations_bypass_mode() return self.get_equations_heat_transfer_mode() @@ -573,7 +573,14 @@ def get_equations_bypass_mode(self) -> list[EquationObject]: ) return equations - def set_internal_energy_equations_bypass(self, equations): + def set_internal_energy_equations_bypass(self, equations: list[EquationObject]) -> None: + r"""Sets the internal energy equations. + + In bypass mode the internal energy of connecting point 0 and 3 are set equal to the node. + This assumes inflow from does connection points into the heat transfer asset. Furthermore, + since the heat transfer asset is in bypass mode the secondary outflow is set equal to + the primary inflow, and vice versa. + """ equations.append(self.get_internal_energy_to_node_equation(connection_point=0)) equations.append(self.get_internal_energy_to_node_equation(connection_point=3)) equation_object = EquationObject() diff --git a/testdata/heat_transfers_test.esdl b/testdata/heat_transfers_test.esdl index 6c711919..e7389917 100644 --- a/testdata/heat_transfers_test.esdl +++ b/testdata/heat_transfers_test.esdl @@ -1,5 +1,5 @@ - + @@ -140,17 +140,17 @@ - - + + - + - + @@ -224,8 +224,8 @@ - - + + @@ -249,8 +249,8 @@ - - + + From 8bb13c29c0984b79f5446ccb4fd542fc98d68ca9 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 15:26:39 +0100 Subject: [PATCH 15/19] moved type ignore to fix erro --- src/omotes_simulator_core/entities/assets/heat_pump.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index f35663c3..83783581 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -153,9 +153,9 @@ def _set_setpoints_secondary(self, setpoints_secondary: Dict) -> None: self.solver_asset.temperature_out_secondary = ( # type: ignore self.temperature_out_secondary ) - self.solver_asset.mass_flow_rate_rate_set_point_secondary = ( + self.solver_asset.mass_flow_rate_rate_set_point_secondary = ( # type: ignore self.mass_flow_secondary - ) # type: ignore + ) self.solver_asset.pre_scribe_mass_flow_secondary = ( # type: ignore self.control_mass_flow_secondary ) From 21d44ba28ac82a1b5b6694331a6e61ccf1c4b19c Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 15:27:13 +0100 Subject: [PATCH 16/19] Fixed formatting --- .../controller/controller_heat_transfer.py | 2 +- .../assets/controller/controller_network.py | 2 +- .../entities/assets/heat_exchanger.py | 17 +++++++++-------- .../entities/assets/heat_pump.py | 2 +- unit_test/entities/test_heat_pump.py | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py index c9d71dfb..2d22a8d9 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_heat_transfer.py @@ -16,11 +16,11 @@ from omotes_simulator_core.entities.assets.asset_defaults import ( PRIMARY, + PROPERTY_BYPASS, PROPERTY_HEAT_DEMAND, PROPERTY_SET_PRESSURE, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, - PROPERTY_BYPASS, SECONDARY, ) from omotes_simulator_core.entities.assets.controller.asset_controller_abstract import ( diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 44de550c..0cb26aaf 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -19,12 +19,12 @@ import numpy as np from omotes_simulator_core.entities.assets.asset_defaults import ( + PRIMARY, PROPERTY_HEAT_DEMAND, PROPERTY_SET_PRESSURE, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, SECONDARY, - PRIMARY, ) from omotes_simulator_core.entities.assets.controller.controller_consumer import ControllerConsumer from omotes_simulator_core.entities.assets.controller.controller_heat_transfer import ( diff --git a/src/omotes_simulator_core/entities/assets/heat_exchanger.py b/src/omotes_simulator_core/entities/assets/heat_exchanger.py index 956389ab..42a89599 100644 --- a/src/omotes_simulator_core/entities/assets/heat_exchanger.py +++ b/src/omotes_simulator_core/entities/assets/heat_exchanger.py @@ -66,11 +66,11 @@ class HeatExchanger(AssetAbstract): """Heat transfer efficiency of the heat exchanger [W/K].""" def __init__( - self, - asset_name: str, - asset_id: str, - connected_ports: list[str], - heat_transfer_efficiency: float = 1, + self, + asset_name: str, + asset_id: str, + connected_ports: list[str], + heat_transfer_efficiency: float = 1, ) -> None: """Initialize a new HeatExchanger instance. @@ -209,9 +209,10 @@ def write_to_output(self) -> None: PROPERTY_HEAT_POWER_PRIMARY: ( self.solver_asset.get_heat_power_primary() # type: ignore ), - PROPERTY_HEAT_LOSS: (self.solver_asset.get_heat_power_primary() # type: ignore - - self.solver_asset.get_heat_power_secondary() # type: ignore - ), + PROPERTY_HEAT_LOSS: ( + self.solver_asset.get_heat_power_primary() # type: ignore + - self.solver_asset.get_heat_power_secondary() # type: ignore + ), } ) diff --git a/src/omotes_simulator_core/entities/assets/heat_pump.py b/src/omotes_simulator_core/entities/assets/heat_pump.py index 83783581..7a529e25 100644 --- a/src/omotes_simulator_core/entities/assets/heat_pump.py +++ b/src/omotes_simulator_core/entities/assets/heat_pump.py @@ -21,6 +21,7 @@ from omotes_simulator_core.entities.assets.asset_defaults import ( DEFAULT_PRESSURE, PRIMARY, + PROPERTY_BYPASS, PROPERTY_ELECTRICITY_CONSUMPTION, PROPERTY_HEAT_DEMAND, PROPERTY_HEAT_POWER_PRIMARY, @@ -29,7 +30,6 @@ PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, SECONDARY, - PROPERTY_BYPASS, ) from omotes_simulator_core.entities.assets.utils import heat_demand_and_temperature_to_mass_flow from omotes_simulator_core.solver.network.assets.heat_transfer_asset import HeatTransferAsset diff --git a/unit_test/entities/test_heat_pump.py b/unit_test/entities/test_heat_pump.py index 077e3ef0..a7b10f39 100644 --- a/unit_test/entities/test_heat_pump.py +++ b/unit_test/entities/test_heat_pump.py @@ -19,6 +19,7 @@ from omotes_simulator_core.entities.assets.asset_defaults import ( PRIMARY, + PROPERTY_BYPASS, PROPERTY_ELECTRICITY_CONSUMPTION, PROPERTY_HEAT_DEMAND, PROPERTY_HEAT_POWER_PRIMARY, @@ -27,7 +28,6 @@ PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, SECONDARY, - PROPERTY_BYPASS, ) from omotes_simulator_core.entities.assets.heat_pump import HeatPump @@ -211,7 +211,7 @@ def test_set_setpoints_calls_both_primary_and_secondary(self): SECONDARY + PROPERTY_HEAT_DEMAND: 1000, PRIMARY + PROPERTY_SET_PRESSURE: False, SECONDARY + PROPERTY_SET_PRESSURE: False, - PROPERTY_BYPASS : False, + PROPERTY_BYPASS: False, } # Act From ce6bd0ac6c708e45684d1eef1a4aa14507bde43e Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 15:33:01 +0100 Subject: [PATCH 17/19] Fixed final linting problem --- .../entities/assets/controller/controller_network.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 0cb26aaf..18a4e5fc 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -235,16 +235,17 @@ def get_total_supply_priority(self, priority: int) -> float: ) def set_pressure(self) -> tuple[str, str]: - """Returns the id of the asset for which the pressure can be set and the key in the set points dict. + """Returns the id of the asset for which the pressure can be set the key The controller needs to set per hydraulic separated part of the system the pressure. The network can thus pass back the id for which asset the pressure needs to be set. - The controller can then do this. + The controller can then add this in the set points dicts. For heattransfer assets also the + controller key needs to be return, either primary or secondary. """ if self.producers: - return (self.producers[0].id, PROPERTY_SET_PRESSURE) + return self.producers[0].id, PROPERTY_SET_PRESSURE if self.heat_transfer_assets_sec: - return (self.heat_transfer_assets_sec[0].id, SECONDARY + PROPERTY_SET_PRESSURE) + return self.heat_transfer_assets_sec[0].id, SECONDARY + PROPERTY_SET_PRESSURE if self.heat_transfer_assets_prim: - return (self.heat_transfer_assets_prim[0].id, PRIMARY + PROPERTY_SET_PRESSURE) + return self.heat_transfer_assets_prim[0].id, PRIMARY + PROPERTY_SET_PRESSURE raise ValueError("No asset found for which the pressure can be set.") From b6c74cd758e60ffc4d81195a6398d78061f1c412 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 17:10:29 +0100 Subject: [PATCH 18/19] Small fixes in logic of ATES --- src/omotes_simulator_core/entities/assets/ates_cluster.py | 2 +- unit_test/entities/test_ates_cluster.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index e0ac046b..3bf0cf8a 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -146,7 +146,7 @@ def _calculate_massflowrate(self) -> None: def _set_solver_asset_setpoint(self) -> None: """Set the setpoint of solver asset.""" - if self.mass_flowrate >= 0: + if self.mass_flowrate <= 0: self.solver_asset.supply_temperature = self.hot_well_temperature # production else: self.solver_asset.supply_temperature = self.cold_well_temperature # injection diff --git a/unit_test/entities/test_ates_cluster.py b/unit_test/entities/test_ates_cluster.py index b06059f8..adae5b27 100644 --- a/unit_test/entities/test_ates_cluster.py +++ b/unit_test/entities/test_ates_cluster.py @@ -74,7 +74,7 @@ def test_injection_ates(self) -> None: self.ates_cluster.set_setpoints(setpoints=setpoints) # Assert - self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 358.6696, delta=0.1) + self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 358.15, delta=0.1) self.assertAlmostEqual(self.ates_cluster.cold_well_temperature, 290.15, delta=0.1) def test_production_ates(self) -> None: @@ -82,8 +82,8 @@ def test_production_ates(self) -> None: # Arrange setpoints = { PROPERTY_HEAT_DEMAND: -1e6, - PROPERTY_TEMPERATURE_OUT: 35 + 273.15, - PROPERTY_TEMPERATURE_IN: 85 + 273.15, + PROPERTY_TEMPERATURE_OUT: 85 + 273.15, + PROPERTY_TEMPERATURE_IN: 35 + 273.15, } # Act @@ -93,5 +93,5 @@ def test_production_ates(self) -> None: self.ates_cluster.set_setpoints(setpoints=setpoints) # Assert - self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 290.1549, delta=0.1) + self.assertAlmostEqual(self.ates_cluster.hot_well_temperature, 355.54, delta=0.1) self.assertAlmostEqual(self.ates_cluster.cold_well_temperature, 308.17, delta=0.1) From 6a81349042693361a1e9fb8a9be2da1fbbf50a76 Mon Sep 17 00:00:00 2001 From: Sam van der Zwan Date: Wed, 25 Mar 2026 17:27:15 +0100 Subject: [PATCH 19/19] Added missing . --- .../entities/assets/controller/controller_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_network.py b/src/omotes_simulator_core/entities/assets/controller/controller_network.py index 18a4e5fc..b4dd4010 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_network.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_network.py @@ -235,7 +235,7 @@ def get_total_supply_priority(self, priority: int) -> float: ) def set_pressure(self) -> tuple[str, str]: - """Returns the id of the asset for which the pressure can be set the key + """Returns the id of the asset for which the pressure can be set the key. The controller needs to set per hydraulic separated part of the system the pressure. The network can thus pass back the id for which asset the pressure needs to be set.