-
Notifications
You must be signed in to change notification settings - Fork 1
288 water to water heat pump bypass mode #344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ae3a6a0
085e6db
30065d2
0d2ff22
23d3025
d6167d0
4300c03
86feca1
1341a82
4c11a17
c661c40
303e5db
ce7226e
40a9cec
8bb13c2
21d44ba
ce6bd0a
b6c74cd
6a81349
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,10 +146,10 @@ 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: | ||
| 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 | ||
| self.solver_asset.mass_flow_rate_set_point = self.mass_flowrate # type: ignore | ||
|
|
||
| def set_setpoints(self, setpoints: dict) -> None: | ||
|
|
@@ -158,9 +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 | ||
| """ | ||
| if self.current_time == self.time: | ||
| return | ||
| self.current_time = self.time | ||
| # Default keys required | ||
| necessary_setpoints = { | ||
| PROPERTY_TEMPERATURE_IN, | ||
|
|
@@ -173,21 +170,31 @@ def set_setpoints(self, setpoints: dict) -> None: | |
| if necessary_setpoints.issubset(setpoints_set): | ||
samvanderzwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.thermal_power_allocation = -1 * setpoints[PROPERTY_HEAT_DEMAND] | ||
| if self.first_time_step: | ||
| self.temperature_in = setpoints[PROPERTY_TEMPERATURE_IN] | ||
| self.temperature_out = setpoints[PROPERTY_TEMPERATURE_OUT] | ||
| # 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kan je een kort comment toevoegen wat je hier aan het doen bent (ivm verwarring over sign/property)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gedaan |
||
| 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() | ||
| 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 +322,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) < ( | ||
| abs(self.thermal_power_allocation) * 0.001 | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,10 +14,9 @@ | |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
| """Module containing the class for a heat trasnfer asset.""" | ||
|
|
||
| import numpy as np | ||
|
|
||
| from omotes_simulator_core.entities.assets.asset_defaults import ( | ||
| PRIMARY, | ||
| PROPERTY_BYPASS, | ||
| PROPERTY_HEAT_DEMAND, | ||
| PROPERTY_SET_PRESSURE, | ||
| PROPERTY_TEMPERATURE_IN, | ||
|
|
@@ -41,26 +40,39 @@ 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ik heb hier wel een vraag over. Zetten we nu standaard temperaturen in dit asset met set_asset of is dit alleen een initialisatie stap?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Klopt de volgorder van de temperaturen; ik zou verwachtten dat PRIM_OUT gleijk moet zijn aan SEC_IN. Dit lijkt nu niet het geval?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ja temepraturen worden nu hard gezet, |
||
| return { | ||
| self.id: { | ||
| PRIMARY + PROPERTY_HEAT_DEMAND: heat_demand, | ||
| 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, | ||
| 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_TEMPERATURE_OUT: 273.15 + 30, | ||
| PRIMARY + PROPERTY_TEMPERATURE_IN: 273.15 + 50, | ||
| 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, | ||
| PRIMARY + PROPERTY_SET_PRESSURE: False, | ||
| PROPERTY_BYPASS: False, | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,11 +16,15 @@ | |
|
|
||
| import datetime | ||
|
|
||
| import numpy as np | ||
|
|
||
| from omotes_simulator_core.entities.assets.asset_defaults import ( | ||
samvanderzwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| PRIMARY, | ||
| PROPERTY_HEAT_DEMAND, | ||
| PROPERTY_SET_PRESSURE, | ||
| PROPERTY_TEMPERATURE_IN, | ||
| PROPERTY_TEMPERATURE_OUT, | ||
| SECONDARY, | ||
| ) | ||
| from omotes_simulator_core.entities.assets.controller.controller_consumer import ControllerConsumer | ||
| from omotes_simulator_core.entities.assets.controller.controller_heat_transfer import ( | ||
|
|
@@ -49,7 +53,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 +73,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,39 +95,39 @@ 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 ( | ||
| return float( | ||
| sum([consumer.get_heat_demand(time) for consumer in self.consumers]) | ||
| * self.factor_to_first_network | ||
| * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) | ||
| ) | ||
|
|
||
| def get_total_discharge_storage(self) -> float: | ||
| """Method to get the total storage discharge of the network corrected to the first network. | ||
|
|
||
| :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]) | ||
| * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) | ||
| ) | ||
|
|
||
| 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]) | ||
| * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) | ||
| ) | ||
|
|
||
| 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])) | ||
| * self.factor_to_first_network | ||
| return float( | ||
| sum([producer.power for producer in self.producers]) | ||
| * float(np.prod(np.array(self.factor_to_first_network, dtype=float))) | ||
| ) | ||
|
|
||
| def set_supply_to_max(self, priority: int = 0) -> dict: | ||
|
|
@@ -166,6 +170,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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Waarom heb je de temperatuur nodig voor een storage? Deze wordt toch niet geset maar gebasseerd op de internal state?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ATES gebruikt ze om mass flow uit te rekenen. en ideal storage zal dat ook doen. In iede geval voor initiele gok |
||
| PROPERTY_TEMPERATURE_IN: storage.temperature_in, | ||
| } | ||
| return storage_settings | ||
|
|
||
|
|
@@ -180,6 +186,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 | ||
|
|
||
|
|
@@ -226,15 +234,18 @@ 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 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.heat_transfer_assets_sec: | ||
| return self.heat_transfer_assets_sec[0].id | ||
| 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, SECONDARY + PROPERTY_SET_PRESSURE | ||
| if self.heat_transfer_assets_prim: | ||
| return self.heat_transfer_assets_prim[0].id, PRIMARY + PROPERTY_SET_PRESSURE | ||
| raise ValueError("No asset found for which the pressure can be set.") | ||
Uh oh!
There was an error while loading. Please reload this page.