From ef38c63e0b56a199930df6283efbaad59ed250f3 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Thu, 20 Nov 2025 15:40:59 +0100 Subject: [PATCH 01/10] implement NVOE calculation to avoid sand production --- .../entities/assets/ates_cluster.py | 178 +++++++++++++++++- .../assets/controller/controller_storage.py | 6 + 2 files changed, 174 insertions(+), 10 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index a9d71176..992cee44 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -37,6 +37,7 @@ kelvin_to_celcius, ) from omotes_simulator_core.solver.network.assets.production_asset import HeatBoundary +from omotes_simulator_core.solver.utils.fluid_properties import fluid_props logger = logging.getLogger(__name__) @@ -129,6 +130,9 @@ def __init__( self.salinity = salinity # ppm self.well_casing_size = well_casing_size # inch self.well_distance = well_distance # meters + self.wellbore_diameter = 31 # inch + self.max_charge_volume_flow = 500 + self.max_discharge_volume_flow = 500 # Output list self.output: list = [] @@ -237,7 +241,7 @@ def _init_rosim(self) -> None: AQUIFER_PERM_Z = AQUIFER_PERM_XY / self.aquifer_anisotropy SALINITY = self.salinity WELL2_X = self.well_distance + 300 - CASING_SIZE = self.well_casing_size + CASING_SIZE = self.wellbore_diameter xml_str = xml_str.replace("$NZ$", str(NZ)) xml_str = xml_str.replace("$MODEL_TOP$", str(MODEL_TOP)) @@ -281,30 +285,184 @@ def _init_rosim(self) -> None: def _run_rosim(self) -> None: """Function to calculate storage temperature after injection and production.""" - volume_flow = self.mass_flowrate * 3600 / 1027 # convert to second and hardcoded saline - # density needs to change with PVT calculation + saline_density = self._get_saline_density( + 20, kelvin_to_celcius((self.hot_well_temperature + self.cold_well_temperature) / 2) + ) + + volume_flow = self.mass_flowrate * 3600 / saline_density # convert to second and + if volume_flow > 0: + volume_flow = min(volume_flow, self.max_charge_volume_flow) + if volume_flow < 0: + volume_flow = -1 * min(abs(volume_flow), self.max_discharge_volume_flow) + + # hardcoded saline timestep = self.time_step / 3600 # convert to hours - rosim_input__flow = [volume_flow, -1 * volume_flow] # first elemnt is for producer well - # and second element is for injection well, positive flow is going upward and negative flow - # is downward + rosim_input_flow = [volume_flow, -1 * volume_flow] # the first-element is for hot well + # and the second-element is for cold well. positive flow is charge and negative flow + # is discharge if volume_flow > 0: rosim_input_temperature = [kelvin_to_celcius(self.temperature_in), -1] # Celcius, -1 in - # injection well to make sure it is not used + # injection well to make sure it is not used elif volume_flow < 0: rosim_input_temperature = [ -1, kelvin_to_celcius(self.temperature_out), ] # Celcius, -1 in - # producer well to make sure it is not used + # producer well to make sure it is not used else: rosim_input_temperature = [-1, -1] # -1 in both producer and injection well to make - # sure it is not used + # sure it is not used ates_temperature = self.rosim.calcTimeStepAndGetTemps( - rosim_input__flow, rosim_input_temperature, timestep + rosim_input_flow, rosim_input_temperature, timestep ) 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_state(self): + """Function to calculate the maximum charge and discharge rate based on NVOE.""" + P = self.aquifer_depth * 0.1 # bar assume pressure increase 1 bar per 10 m depth + + average_temperature = (self.temperature_in + self.temperature_out) / 2 + water_density = fluid_props.get_density(average_temperature) + water_heat_capacity = fluid_props.get_heat_capacity(average_temperature) + + max_extraction_flow_cold_well = self._get_max_flowrate_extraction_norm( + P, kelvin_to_celcius(self.cold_well_temperature) + ) + max_injection_flow_cold_well = self._get_max_flowrate_injection_norm( + P, kelvin_to_celcius(self.cold_well_temperature) + ) + + max_extraction_flow_hot_well = self._get_max_flowrate_extraction_norm( + P, kelvin_to_celcius(self.hot_well_temperature) + ) + max_injection_flow_hot_well = self._get_max_flowrate_injection_norm( + P, kelvin_to_celcius(self.hot_well_temperature) + ) + + self.max_charge_volume_flow = min( + max_extraction_flow_cold_well, max_injection_flow_hot_well + ) + self.max_discharge_volume_flow = min( + max_injection_flow_cold_well, max_extraction_flow_hot_well + ) + + max_charge_power = ( + (self.hot_well_temperature - self.cold_well_temperature) + * self.max_charge_volume_flow + * water_density + / 3600 + * water_heat_capacity + ) + + max_discharge_power = ( + (self.hot_well_temperature - self.cold_well_temperature) + * self.max_discharge_volume_flow + * water_density + / 3600 + * water_heat_capacity + ) + + return {"max_charge_power": max_charge_power, "max_discharge_power": max_discharge_power} + + def _get_max_flowrate_extraction_norm(self, P, T) -> float: + """Function to calculate the maximum flowrate of production in norm.""" + grav_accel = 9.81 # m/s2 + saline_density = self._get_saline_density(P, T) + saline_viscosity = self._get_saline_viscosity(P, T) + aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 + # diameter + well_radius = 0.5 * self.wellbore_diameter * 0.0254 # m + + max_extract_flow_velocity = ( + 2 * 60 * 60 * aquifer_permeability * saline_density * grav_accel / saline_viscosity + ) # m/h + + max_flowrate = ( + 2 * math.pi * well_radius * self.aquifer_thickness * max_extract_flow_velocity + ) + + max_flowrate = max_flowrate * self.aquifer_depth * 0.01 # using depth factor because ATES + # is deeper than WKO + + return max_flowrate + + def _get_max_flowrate_injection_norm(self, P, T) -> float: + """Function to calculate the maximum flowrate of injection in norm.""" + grav_accel = 9.81 # m/s2 + saline_density = self._get_saline_density(P, T) + saline_viscosity = self._get_saline_viscosity(P, T) + aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 + # diameter + well_radius = 0.5 * self.wellbore_diameter * 0.0254 # m + + cloggingVel = 0.3 + membraneFilterIndex = 0.1 + equivLoadHoursPerYear = 3500 + max_infiltrate_flow_velocity = ( + 1000 + * math.pow( + 576 * aquifer_permeability * saline_density * grav_accel / saline_viscosity, 0.6 + ) + * math.sqrt(cloggingVel / (2 * membraneFilterIndex * equivLoadHoursPerYear)) + ) + + max_flowrate = ( + 2 * math.pi * well_radius * self.aquifer_thickness * max_infiltrate_flow_velocity + ) # m/h + + return max_flowrate + + def _get_saline_density(self, P, T) -> float: + """Function to calculate the saline density.""" + P = P * 1e5 * 1e-6 # Bar to MPa + S = self.salinity * 1e-6 # ppm to kg/kg + + density_fresh = 1 + 1e-6 * ( + -80.0 * T + - 3.3 * T * T + + 0.00175 * T * T * T + + 489.0 * P + - 2.0 * T * P + + 0.016 * T * T * P + - 1.3e-5 * T * T * T * P + - 0.333 * P * P + - 0.002 * T * P * P + ) + + density = density_fresh + S * ( + 0.668 + + 0.44 * S + + 1e-6 + * ( + 300.0 * P + - 2400.0 * P * S + + T * (80.0 + 3.0 * T - 3300.0 * S - 13.0 * P + 47.0 * P * S) + ) + ) + + density = density * 1000 # g/cm3 to kg/m3 + + return density + + def _get_saline_viscosity(self, P, T) -> float: + """Function to calculate the saline viscosity.""" + P = P * 1e5 * 1e-6 # Bar to MPa + S = self.salinity * 1e-6 # ppm to kg/kg + + viscosity = ( + 0.1 + + 0.333 * S + + (1.65 + 91.90 * S * S * S) + * math.exp( + -(0.42 * math.pow((math.pow(S, 0.8) - 0.17), 2.0) + 0.045) * math.pow(T, 0.8) + ) + ) + + viscosity = viscosity * 1e-3 # cP to Pas + + return viscosity diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py index cbcca453..dacad0f5 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py @@ -76,3 +76,9 @@ def get_heat_power(self, time: datetime.datetime) -> float: else: return float(self.profile["values"][index]) return 0 + + def set_state(self, state): + """Update maximum charge and discharge power.""" + if bool(state): + self.max_charge_power = state["max_charge_power"] + self.max_discharge_power = state["max_discharge_power"] From f4fc582443d392fc98c36b74e218ce1b74ab0132 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Thu, 20 Nov 2025 15:49:15 +0100 Subject: [PATCH 02/10] fix typecheck and linting --- .../entities/assets/ates_cluster.py | 16 ++++++++-------- .../assets/controller/controller_storage.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 992cee44..242537e3 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -130,9 +130,9 @@ def __init__( self.salinity = salinity # ppm self.well_casing_size = well_casing_size # inch self.well_distance = well_distance # meters - self.wellbore_diameter = 31 # inch - self.max_charge_volume_flow = 500 - self.max_discharge_volume_flow = 500 + self.wellbore_diameter = 31.0 # inch + self.max_charge_volume_flow = 500.0 + self.max_discharge_volume_flow = 500.0 # Output list self.output: list = [] @@ -322,7 +322,7 @@ 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_state(self): + def get_state(self) -> dict[str, float]: """Function to calculate the maximum charge and discharge rate based on NVOE.""" P = self.aquifer_depth * 0.1 # bar assume pressure increase 1 bar per 10 m depth @@ -369,7 +369,7 @@ def get_state(self): return {"max_charge_power": max_charge_power, "max_discharge_power": max_discharge_power} - def _get_max_flowrate_extraction_norm(self, P, T) -> float: + def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: """Function to calculate the maximum flowrate of production in norm.""" grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) @@ -391,7 +391,7 @@ def _get_max_flowrate_extraction_norm(self, P, T) -> float: return max_flowrate - def _get_max_flowrate_injection_norm(self, P, T) -> float: + def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: """Function to calculate the maximum flowrate of injection in norm.""" grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) @@ -417,7 +417,7 @@ def _get_max_flowrate_injection_norm(self, P, T) -> float: return max_flowrate - def _get_saline_density(self, P, T) -> float: + def _get_saline_density(self, P: float, T: float) -> float: """Function to calculate the saline density.""" P = P * 1e5 * 1e-6 # Bar to MPa S = self.salinity * 1e-6 # ppm to kg/kg @@ -449,7 +449,7 @@ def _get_saline_density(self, P, T) -> float: return density - def _get_saline_viscosity(self, P, T) -> float: + def _get_saline_viscosity(self, P: float, T: float) -> float: """Function to calculate the saline viscosity.""" P = P * 1e5 * 1e-6 # Bar to MPa S = self.salinity * 1e-6 # ppm to kg/kg diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py index dacad0f5..da66a96d 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py @@ -77,7 +77,7 @@ def get_heat_power(self, time: datetime.datetime) -> float: return float(self.profile["values"][index]) return 0 - def set_state(self, state): + def set_state(self, state: dict[str, float]) -> None: """Update maximum charge and discharge power.""" if bool(state): self.max_charge_power = state["max_charge_power"] From 9dd033978613dcdcb107258d5196c53c3fae4c75 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 28 Nov 2025 09:26:25 +0100 Subject: [PATCH 03/10] implement feedback for review --- .../esdl_asset_mappers/ates_mapper.py | 3 - .../entities/assets/asset_defaults.py | 3 +- .../entities/assets/ates_cluster.py | 147 ++++++++++-------- unit_test/entities/test_ates_cluster.py | 1 - 4 files changed, 84 insertions(+), 70 deletions(-) diff --git a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py index 76685d66..96f837cb 100644 --- a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py +++ b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py @@ -66,9 +66,6 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> AssetAbstract: salinity=esdl_asset.get_property( esdl_property_name="salinity", default_value=ATES_DEFAULTS.salinity ), - well_casing_size=esdl_asset.get_property( - esdl_property_name="wellCasingSize", default_value=ATES_DEFAULTS.well_casing_size - ), well_distance=esdl_asset.get_property( esdl_property_name="wellDistance", default_value=ATES_DEFAULTS.well_distance ), diff --git a/src/omotes_simulator_core/entities/assets/asset_defaults.py b/src/omotes_simulator_core/entities/assets/asset_defaults.py index 12dbad0d..9a7f0d0a 100644 --- a/src/omotes_simulator_core/entities/assets/asset_defaults.py +++ b/src/omotes_simulator_core/entities/assets/asset_defaults.py @@ -78,10 +78,11 @@ class AtesDefaults: aquifer_permeability: float = 10000.0 # mD aquifer_anisotropy: float = 4.0 # - salinity: float = 10000.0 # ppm - well_casing_size: float = 13.0 # inch + well_casing_size: float = 13.0 * 0.0254 # meters well_distance: float = 150.0 # meters maximum_flow_charge: float = 200.0 # m3/h maximum_flow_discharge: float = 200.0 # m3/h + wellbore_size: float = 31.0 * 0.0254 # meters @dataclass diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 242537e3..ffc8e886 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -29,6 +29,7 @@ PROPERTY_PRESSURE_SUPPLY, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, + ATES_DEFAULTS ) from omotes_simulator_core.entities.assets.pyjnius_loader import PyjniusLoader from omotes_simulator_core.entities.assets.utils import ( @@ -103,7 +104,6 @@ def __init__( aquifer_permeability: float, aquifer_anisotropy: float, salinity: float, - well_casing_size: float, well_distance: float, ) -> None: """Initialize a AtesCluster object. @@ -128,9 +128,8 @@ def __init__( self.aquifer_permeability = aquifer_permeability # mD self.aquifer_anisotropy = aquifer_anisotropy # - self.salinity = salinity # ppm - self.well_casing_size = well_casing_size # inch self.well_distance = well_distance # meters - self.wellbore_diameter = 31.0 # inch + self.wellbore_diameter = ATES_DEFAULTS.wellbore_size # meters self.max_charge_volume_flow = 500.0 self.max_discharge_volume_flow = 500.0 @@ -226,38 +225,38 @@ def _init_rosim(self) -> None: # overwrite template value with ESDL properties for ROSIM input - MODEL_TOP = self.aquifer_depth - 100 - AQUIFER_THICKNESS = self.aquifer_thickness - NZ_AQUIFER = math.floor(AQUIFER_THICKNESS / 2) - NZ = NZ_AQUIFER + 8 - AQUIFER_TOP = self.aquifer_depth - AQUIFER_BASE = self.aquifer_depth + self.aquifer_thickness - SURFACE_TEMPERATURE = self.aquifer_mid_temperature - 0.034 * ( + model_top = self.aquifer_depth - 100 + aquifer_thickness = self.aquifer_thickness + nz_aquifer = math.floor(aquifer_thickness / 2) + nz = nz_aquifer + 8 + aquifer_top = self.aquifer_depth + aquifer_base = self.aquifer_depth + self.aquifer_thickness + surface_temperature = self.aquifer_mid_temperature - 0.034 * ( self.aquifer_depth + self.aquifer_thickness / 2 ) - AQUIFER_NTG = self.aquifer_net_to_gross - AQUIFER_PORO = self.aquifer_porosity - AQUIFER_PERM_XY = self.aquifer_permeability - AQUIFER_PERM_Z = AQUIFER_PERM_XY / self.aquifer_anisotropy - SALINITY = self.salinity - WELL2_X = self.well_distance + 300 - CASING_SIZE = self.wellbore_diameter - - xml_str = xml_str.replace("$NZ$", str(NZ)) - xml_str = xml_str.replace("$MODEL_TOP$", str(MODEL_TOP)) + aquifer_ntg = self.aquifer_net_to_gross + aquifer_poro = self.aquifer_porosity + aquifer_perm_xy = self.aquifer_permeability + aquifer_perm_z = aquifer_perm_xy / self.aquifer_anisotropy + salinity = self.salinity + well2_x = self.well_distance + 300 + wellbore_diameter = self.wellbore_diameter / 0.0254 # convert to inch + + xml_str = xml_str.replace("$NZ$", str(nz)) + xml_str = xml_str.replace("$MODEL_TOP$", str(model_top)) xml_str = xml_str.replace("$TIME_STEP_UNIT$", str(2)) - xml_str = xml_str.replace("$WELL2_X$", str(WELL2_X)) - xml_str = xml_str.replace("$AQUIFER_TOP$", str(AQUIFER_TOP)) - xml_str = xml_str.replace("$AQUIFER_BASE$", str(AQUIFER_BASE)) - xml_str = xml_str.replace("$CASING_SIZE$", str(CASING_SIZE)) - xml_str = xml_str.replace("$SURFACE_TEMPERATURE$", str(SURFACE_TEMPERATURE)) - xml_str = xml_str.replace("$SALINITY$", str(SALINITY)) - xml_str = xml_str.replace("$NZ_AQUIFER$", str(NZ_AQUIFER)) - xml_str = xml_str.replace("$AQUIFER_THICKNESS$", str(AQUIFER_THICKNESS)) - xml_str = xml_str.replace("$AQUIFER_PORO$", str(AQUIFER_PORO)) - xml_str = xml_str.replace("$AQUIFER_NTG$", str(AQUIFER_NTG)) - xml_str = xml_str.replace("$AQUIFER_PERM_XY$", str(AQUIFER_PERM_XY)) - xml_str = xml_str.replace("$AQUIFER_PERM_Z$", str(AQUIFER_PERM_Z)) + xml_str = xml_str.replace("$WELL2_X$", str(well2_x)) + xml_str = xml_str.replace("$AQUIFER_TOP$", str(aquifer_top)) + xml_str = xml_str.replace("$AQUIFER_BASE$", str(aquifer_base)) + xml_str = xml_str.replace("$CASING_SIZE$", str(wellbore_diameter)) + xml_str = xml_str.replace("$SURFACE_TEMPERATURE$", str(surface_temperature)) + xml_str = xml_str.replace("$SALINITY$", str(salinity)) + xml_str = xml_str.replace("$NZ_AQUIFER$", str(nz_aquifer)) + xml_str = xml_str.replace("$AQUIFER_THICKNESS$", str(aquifer_thickness)) + xml_str = xml_str.replace("$AQUIFER_PORO$", str(aquifer_poro)) + xml_str = xml_str.replace("$AQUIFER_NTG$", str(aquifer_ntg)) + xml_str = xml_str.replace("$AQUIFER_PERM_XY$", str(aquifer_perm_xy)) + xml_str = xml_str.replace("$AQUIFER_PERM_Z$", str(aquifer_perm_z)) temp_xmlfile_path = os.path.join(path, "bin/ates_sequential_temp.xml") with open(temp_xmlfile_path, "w") as temp_xmlfile: @@ -285,9 +284,12 @@ def _init_rosim(self) -> None: def _run_rosim(self) -> None: """Function to calculate storage temperature after injection and production.""" + downhole_pressure = self.aquifer_depth * 1e4 # Pa - assume pressure increase by 1e4 Pa + # per 10 m depth + saline_density = self._get_saline_density( - 20, kelvin_to_celcius((self.hot_well_temperature + self.cold_well_temperature) / 2) - ) + downhole_pressure, kelvin_to_celcius((self.hot_well_temperature + + self.cold_well_temperature) / 2)) volume_flow = self.mass_flowrate * 3600 / saline_density # convert to second and if volume_flow > 0: @@ -298,8 +300,8 @@ def _run_rosim(self) -> None: # hardcoded saline timestep = self.time_step / 3600 # convert to hours - rosim_input_flow = [volume_flow, -1 * volume_flow] # the first-element is for hot well - # and the second-element is for cold well. positive flow is charge and negative flow + rosim_input_flow = [volume_flow, -1 * volume_flow] # the first element is for hot well + # and the second element is for cold well. positive flow is charge and negative flow # is discharge if volume_flow > 0: @@ -324,24 +326,25 @@ def _run_rosim(self) -> None: def get_state(self) -> dict[str, float]: """Function to calculate the maximum charge and discharge rate based on NVOE.""" - P = self.aquifer_depth * 0.1 # bar assume pressure increase 1 bar per 10 m depth + downhole_pressure = self.aquifer_depth * 1e4 # Pa - assume pressure increase 1e4 Pa + # per 10 m depth average_temperature = (self.temperature_in + self.temperature_out) / 2 water_density = fluid_props.get_density(average_temperature) water_heat_capacity = fluid_props.get_heat_capacity(average_temperature) max_extraction_flow_cold_well = self._get_max_flowrate_extraction_norm( - P, kelvin_to_celcius(self.cold_well_temperature) + downhole_pressure, kelvin_to_celcius(self.cold_well_temperature) ) max_injection_flow_cold_well = self._get_max_flowrate_injection_norm( - P, kelvin_to_celcius(self.cold_well_temperature) + downhole_pressure, kelvin_to_celcius(self.cold_well_temperature) ) max_extraction_flow_hot_well = self._get_max_flowrate_extraction_norm( - P, kelvin_to_celcius(self.hot_well_temperature) + downhole_pressure, kelvin_to_celcius(self.hot_well_temperature) ) max_injection_flow_hot_well = self._get_max_flowrate_injection_norm( - P, kelvin_to_celcius(self.hot_well_temperature) + downhole_pressure, kelvin_to_celcius(self.hot_well_temperature) ) self.max_charge_volume_flow = min( @@ -370,39 +373,48 @@ def get_state(self) -> dict[str, float]: return {"max_charge_power": max_charge_power, "max_discharge_power": max_discharge_power} def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: - """Function to calculate the maximum flowrate of production in norm.""" + """Function to calculate the maximum flowrate of production in norm. + + reference equation: NVOE Richtlijnen Ondergrondse Energieopslag, november 2006 + parameters value: https://www.thermogis.nl/doublet-en-economische-parameters-hto + """ grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 # diameter - well_radius = 0.5 * self.wellbore_diameter * 0.0254 # m + well_radius = 0.5 * self.wellbore_diameter # m max_extract_flow_velocity = ( 2 * 60 * 60 * aquifer_permeability * saline_density * grav_accel / saline_viscosity - ) # m/h + ) # m3/h max_flowrate = ( 2 * math.pi * well_radius * self.aquifer_thickness * max_extract_flow_velocity ) - max_flowrate = max_flowrate * self.aquifer_depth * 0.01 # using depth factor because ATES - # is deeper than WKO + max_flowrate = max_flowrate * self.aquifer_depth * 0.01 # using a depth factor multiplier + # (0.01, based on expert judgment) as a function of depth, because ATES is deeper than WKO + # and therefore allows a higher flowrate. return max_flowrate def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: - """Function to calculate the maximum flowrate of injection in norm.""" + """Function to calculate the maximum flowrate of injection in norm. + + reference equation: NVOE Richtlijnen Ondergrondse Energieopslag, november 2006 + parameters value: https://www.thermogis.nl/doublet-en-economische-parameters-hto + """ grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 # diameter - well_radius = 0.5 * self.wellbore_diameter * 0.0254 # m + well_radius = 0.5 * self.wellbore_diameter # m, radius is half of diameter - cloggingVel = 0.3 - membraneFilterIndex = 0.1 - equivLoadHoursPerYear = 3500 + cloggingVel = 0.3 # meter/year + membraneFilterIndex = 0.1 # s/l2 + equivLoadHoursPerYear = 3500 # hours max_infiltrate_flow_velocity = ( 1000 * math.pow( @@ -413,25 +425,28 @@ def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: max_flowrate = ( 2 * math.pi * well_radius * self.aquifer_thickness * max_infiltrate_flow_velocity - ) # m/h + ) # m3/h return max_flowrate def _get_saline_density(self, P: float, T: float) -> float: - """Function to calculate the saline density.""" - P = P * 1e5 * 1e-6 # Bar to MPa + """Function to calculate the saline density. + + Input is pressure in Pa and temperature in Celsius. Output is density in kg/m3. + """ + P_MPa = P * 1e-6 # Bar to MPa S = self.salinity * 1e-6 # ppm to kg/kg density_fresh = 1 + 1e-6 * ( -80.0 * T - 3.3 * T * T + 0.00175 * T * T * T - + 489.0 * P - - 2.0 * T * P - + 0.016 * T * T * P - - 1.3e-5 * T * T * T * P - - 0.333 * P * P - - 0.002 * T * P * P + + 489.0 * P_MPa + - 2.0 * T * P_MPa + + 0.016 * T * T * P_MPa + - 1.3e-5 * T * T * T * P_MPa + - 0.333 * P_MPa * P_MPa + - 0.002 * T * P_MPa * P_MPa ) density = density_fresh + S * ( @@ -439,9 +454,9 @@ def _get_saline_density(self, P: float, T: float) -> float: + 0.44 * S + 1e-6 * ( - 300.0 * P - - 2400.0 * P * S - + T * (80.0 + 3.0 * T - 3300.0 * S - 13.0 * P + 47.0 * P * S) + 300.0 * P_MPa + - 2400.0 * P_MPa * S + + T * (80.0 + 3.0 * T - 3300.0 * S - 13.0 * P_MPa + 47.0 * P_MPa * S) ) ) @@ -450,8 +465,10 @@ def _get_saline_density(self, P: float, T: float) -> float: return density def _get_saline_viscosity(self, P: float, T: float) -> float: - """Function to calculate the saline viscosity.""" - P = P * 1e5 * 1e-6 # Bar to MPa + """Function to calculate the saline viscosity using Batzle-Wang correlation. + + Input is pressure in Pa and temperature in Celsius. Output is viscosity in Pas. + """ S = self.salinity * 1e-6 # ppm to kg/kg viscosity = ( diff --git a/unit_test/entities/test_ates_cluster.py b/unit_test/entities/test_ates_cluster.py index 43bc0698..0e038381 100644 --- a/unit_test/entities/test_ates_cluster.py +++ b/unit_test/entities/test_ates_cluster.py @@ -54,7 +54,6 @@ def setUp(self) -> None: aquifer_permeability=self.aquifer_permeability, aquifer_anisotropy=self.aquifer_anisotropy, salinity=self.salinity, - well_casing_size=self.well_casing_size, well_distance=self.well_distance, ) From 9979f4b56430d3d264072b341bc9e4b73c8eeb19 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 28 Nov 2025 11:47:16 +0100 Subject: [PATCH 04/10] create new function to calculate maximum charge and discharge rate --- .../entities/assets/ates_cluster.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index ffc8e886..0e7d7568 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -288,8 +288,7 @@ def _run_rosim(self) -> None: # per 10 m depth saline_density = self._get_saline_density( - downhole_pressure, kelvin_to_celcius((self.hot_well_temperature - + self.cold_well_temperature) / 2)) + downhole_pressure, (self.hot_well_temperature + self.cold_well_temperature) / 2) volume_flow = self.mass_flowrate * 3600 / saline_density # convert to second and if volume_flow > 0: @@ -325,7 +324,13 @@ def _run_rosim(self) -> None: self.cold_well_temperature = celcius_to_kelvin(ates_temperature[1]) # convert to K def get_state(self) -> dict[str, float]: - """Function to calculate the maximum charge and discharge rate based on NVOE.""" + """Function to get the state of ATES at current timestep.""" + max_charge_power, max_discharge_power = self._calculate_max_charge_discharge_power() + + return {"max_charge_power": max_charge_power, "max_discharge_power": max_discharge_power} + + def _calculate_max_charge_discharge_power(self) -> tuple[float, float]: + """Function to calculate the maximum charge power and discharge power based on NVOE.""" downhole_pressure = self.aquifer_depth * 1e4 # Pa - assume pressure increase 1e4 Pa # per 10 m depth @@ -334,17 +339,17 @@ def get_state(self) -> dict[str, float]: water_heat_capacity = fluid_props.get_heat_capacity(average_temperature) max_extraction_flow_cold_well = self._get_max_flowrate_extraction_norm( - downhole_pressure, kelvin_to_celcius(self.cold_well_temperature) + downhole_pressure, self.cold_well_temperature ) max_injection_flow_cold_well = self._get_max_flowrate_injection_norm( - downhole_pressure, kelvin_to_celcius(self.cold_well_temperature) + downhole_pressure, self.cold_well_temperature ) max_extraction_flow_hot_well = self._get_max_flowrate_extraction_norm( - downhole_pressure, kelvin_to_celcius(self.hot_well_temperature) + downhole_pressure, self.hot_well_temperature ) max_injection_flow_hot_well = self._get_max_flowrate_injection_norm( - downhole_pressure, kelvin_to_celcius(self.hot_well_temperature) + downhole_pressure, self.hot_well_temperature ) self.max_charge_volume_flow = min( @@ -370,7 +375,7 @@ def get_state(self) -> dict[str, float]: * water_heat_capacity ) - return {"max_charge_power": max_charge_power, "max_discharge_power": max_discharge_power} + return max_charge_power, max_discharge_power def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: """Function to calculate the maximum flowrate of production in norm. @@ -429,24 +434,25 @@ def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: return max_flowrate - def _get_saline_density(self, P: float, T: float) -> float: + def _get_saline_density(self, P_Pa: float, T_K: float) -> float: """Function to calculate the saline density. Input is pressure in Pa and temperature in Celsius. Output is density in kg/m3. """ - P_MPa = P * 1e-6 # Bar to MPa + P = P_Pa * 1e-6 # Pa to MPa + T = kelvin_to_celcius(T_K) #K to C S = self.salinity * 1e-6 # ppm to kg/kg density_fresh = 1 + 1e-6 * ( -80.0 * T - 3.3 * T * T + 0.00175 * T * T * T - + 489.0 * P_MPa - - 2.0 * T * P_MPa - + 0.016 * T * T * P_MPa - - 1.3e-5 * T * T * T * P_MPa - - 0.333 * P_MPa * P_MPa - - 0.002 * T * P_MPa * P_MPa + + 489.0 * P + - 2.0 * T * P + + 0.016 * T * T * P + - 1.3e-5 * T * T * T * P + - 0.333 * P * P + - 0.002 * T * P * P ) density = density_fresh + S * ( @@ -454,9 +460,9 @@ def _get_saline_density(self, P: float, T: float) -> float: + 0.44 * S + 1e-6 * ( - 300.0 * P_MPa - - 2400.0 * P_MPa * S - + T * (80.0 + 3.0 * T - 3300.0 * S - 13.0 * P_MPa + 47.0 * P_MPa * S) + 300.0 * P + - 2400.0 * P * S + + T * (80.0 + 3.0 * T - 3300.0 * S - 13.0 * P + 47.0 * P * S) ) ) @@ -464,12 +470,13 @@ def _get_saline_density(self, P: float, T: float) -> float: return density - def _get_saline_viscosity(self, P: float, T: float) -> float: + def _get_saline_viscosity(self, P_Pa: float, T_K: float) -> float: """Function to calculate the saline viscosity using Batzle-Wang correlation. Input is pressure in Pa and temperature in Celsius. Output is viscosity in Pas. """ S = self.salinity * 1e-6 # ppm to kg/kg + T = kelvin_to_celcius(T_K) #K to C viscosity = ( 0.1 From 1e8e15e50da6eed0afb7fedd6def13624aa81250 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 28 Nov 2025 12:06:54 +0100 Subject: [PATCH 05/10] reformat text --- .../entities/assets/ates_cluster.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 0e7d7568..de1e1ca0 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -21,6 +21,7 @@ from omotes_simulator_core.entities.assets.asset_abstract import AssetAbstract from omotes_simulator_core.entities.assets.asset_defaults import ( + ATES_DEFAULTS, DEFAULT_TEMPERATURE, DEFAULT_TEMPERATURE_DIFFERENCE, PROPERTY_HEAT_DEMAND, @@ -29,7 +30,6 @@ PROPERTY_PRESSURE_SUPPLY, PROPERTY_TEMPERATURE_IN, PROPERTY_TEMPERATURE_OUT, - ATES_DEFAULTS ) from omotes_simulator_core.entities.assets.pyjnius_loader import PyjniusLoader from omotes_simulator_core.entities.assets.utils import ( @@ -288,7 +288,8 @@ def _run_rosim(self) -> None: # per 10 m depth saline_density = self._get_saline_density( - downhole_pressure, (self.hot_well_temperature + self.cold_well_temperature) / 2) + downhole_pressure, (self.hot_well_temperature + self.cold_well_temperature) / 2 + ) volume_flow = self.mass_flowrate * 3600 / saline_density # convert to second and if volume_flow > 0: @@ -440,7 +441,7 @@ def _get_saline_density(self, P_Pa: float, T_K: float) -> float: Input is pressure in Pa and temperature in Celsius. Output is density in kg/m3. """ P = P_Pa * 1e-6 # Pa to MPa - T = kelvin_to_celcius(T_K) #K to C + T = kelvin_to_celcius(T_K) # K to C S = self.salinity * 1e-6 # ppm to kg/kg density_fresh = 1 + 1e-6 * ( @@ -476,7 +477,7 @@ def _get_saline_viscosity(self, P_Pa: float, T_K: float) -> float: Input is pressure in Pa and temperature in Celsius. Output is viscosity in Pas. """ S = self.salinity * 1e-6 # ppm to kg/kg - T = kelvin_to_celcius(T_K) #K to C + T = kelvin_to_celcius(T_K) # K to C viscosity = ( 0.1 From e269fc675e1cca9bf3731378d4df83daae258361 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Mon, 1 Dec 2025 22:11:31 +0100 Subject: [PATCH 06/10] remove casing size --- .../esdl_asset_mappers/test_esdl_asset_ates_mapper.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/unit_test/adapters/transforms/esdl_asset_mappers/test_esdl_asset_ates_mapper.py b/unit_test/adapters/transforms/esdl_asset_mappers/test_esdl_asset_ates_mapper.py index 353ba154..cee0d070 100644 --- a/unit_test/adapters/transforms/esdl_asset_mappers/test_esdl_asset_ates_mapper.py +++ b/unit_test/adapters/transforms/esdl_asset_mappers/test_esdl_asset_ates_mapper.py @@ -81,10 +81,6 @@ def test_to_entity_method(self): self.assertEqual( ates_entity.salinity, esdl_asset.get_property("salinity", ATES_DEFAULTS.salinity) ) - self.assertEqual( - ates_entity.well_casing_size, - esdl_asset.get_property("wellCasingSize", ATES_DEFAULTS.well_casing_size), - ) self.assertEqual( ates_entity.well_distance, esdl_asset.get_property("wellDistance", ATES_DEFAULTS.well_distance), From 7c3df2cc4e3f803793d482da2daad0f75cda0b7e Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Wed, 17 Dec 2025 10:43:33 +0100 Subject: [PATCH 07/10] fix linting --- .../entities/assets/controller/controller_storage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py index e9fd8f26..7570ffa3 100644 --- a/src/omotes_simulator_core/entities/assets/controller/controller_storage.py +++ b/src/omotes_simulator_core/entities/assets/controller/controller_storage.py @@ -90,4 +90,3 @@ def set_state(self, state: dict[str, float]) -> None: if bool(state): self.max_charge_power = state["max_charge_power"] self.max_discharge_power = state["max_discharge_power"] - \ No newline at end of file From 0a8f4781ee24c65bcced601fa8c302ccb6d3ce31 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 6 Feb 2026 15:30:46 +0100 Subject: [PATCH 08/10] change back to well_casing_size and using new esdl ver 26.2 for maximum flowrate --- .../esdl_asset_mappers/ates_mapper.py | 6 + .../entities/assets/asset_defaults.py | 4 +- .../entities/assets/ates_cluster.py | 21 +-- testdata/test_ates.esdl | 120 +++++++++--------- unit_test/entities/test_ates_cluster.py | 3 + 5 files changed, 84 insertions(+), 70 deletions(-) diff --git a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py index 96f837cb..51fe4d9e 100644 --- a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py +++ b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py @@ -69,6 +69,12 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> AssetAbstract: well_distance=esdl_asset.get_property( esdl_property_name="wellDistance", default_value=ATES_DEFAULTS.well_distance ), + well_casing_size=esdl_asset.get_property( + esdl_property_name="wellCasingSize", default_value=ATES_DEFAULTS.well_casing_size + ), + maximum_flowrate=esdl_asset.get_property( + esdl_property_name="maximumFlowRate", default_value=ATES_DEFAULTS.maximum_flow_rate + ), ) return ates_entity diff --git a/src/omotes_simulator_core/entities/assets/asset_defaults.py b/src/omotes_simulator_core/entities/assets/asset_defaults.py index 9a7f0d0a..a9fe1d7b 100644 --- a/src/omotes_simulator_core/entities/assets/asset_defaults.py +++ b/src/omotes_simulator_core/entities/assets/asset_defaults.py @@ -78,11 +78,11 @@ class AtesDefaults: aquifer_permeability: float = 10000.0 # mD aquifer_anisotropy: float = 4.0 # - salinity: float = 10000.0 # ppm - well_casing_size: float = 13.0 * 0.0254 # meters well_distance: float = 150.0 # meters maximum_flow_charge: float = 200.0 # m3/h maximum_flow_discharge: float = 200.0 # m3/h - wellbore_size: float = 31.0 * 0.0254 # meters + well_casing_size: float = 31.0 * 0.0254 # meters + maximum_flowrate: float = 500.0 # m3/h @dataclass diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index de1e1ca0..17b767a3 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -83,11 +83,14 @@ class AtesCluster(AssetAbstract): """The salinity of the aquifer [ppm].""" well_casing_size: float - """The casing size of the well [inch].""" + """The casing size of the well [m].""" well_distance: float """The distance of the well [m].""" + maximum_flowrate: float + """The maximum flowrate of ATES [m3/h].""" + pyjnius_loader: PyjniusLoader """Loader object to delay importing pyjnius module and Java classes.""" @@ -105,6 +108,8 @@ def __init__( aquifer_anisotropy: float, salinity: float, well_distance: float, + well_casing_size: float, + maximum_flowrate: float, ) -> None: """Initialize a AtesCluster object. @@ -129,9 +134,9 @@ def __init__( self.aquifer_anisotropy = aquifer_anisotropy # - self.salinity = salinity # ppm self.well_distance = well_distance # meters - self.wellbore_diameter = ATES_DEFAULTS.wellbore_size # meters - self.max_charge_volume_flow = 500.0 - self.max_discharge_volume_flow = 500.0 + self.well_casing_size = well_casing_size # meters + self.max_charge_volume_flow = maximum_flowrate + self.max_discharge_volume_flow = maximum_flowrate # Output list self.output: list = [] @@ -240,7 +245,7 @@ def _init_rosim(self) -> None: aquifer_perm_z = aquifer_perm_xy / self.aquifer_anisotropy salinity = self.salinity well2_x = self.well_distance + 300 - wellbore_diameter = self.wellbore_diameter / 0.0254 # convert to inch + well_casing_size = self.well_casing_size / 0.0254 # convert to inch xml_str = xml_str.replace("$NZ$", str(nz)) xml_str = xml_str.replace("$MODEL_TOP$", str(model_top)) @@ -248,7 +253,7 @@ def _init_rosim(self) -> None: xml_str = xml_str.replace("$WELL2_X$", str(well2_x)) xml_str = xml_str.replace("$AQUIFER_TOP$", str(aquifer_top)) xml_str = xml_str.replace("$AQUIFER_BASE$", str(aquifer_base)) - xml_str = xml_str.replace("$CASING_SIZE$", str(wellbore_diameter)) + xml_str = xml_str.replace("$CASING_SIZE$", str(well_casing_size)) xml_str = xml_str.replace("$SURFACE_TEMPERATURE$", str(surface_temperature)) xml_str = xml_str.replace("$SALINITY$", str(salinity)) xml_str = xml_str.replace("$NZ_AQUIFER$", str(nz_aquifer)) @@ -389,7 +394,7 @@ def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 # diameter - well_radius = 0.5 * self.wellbore_diameter # m + well_radius = 0.5 * self.well_casing_size # m max_extract_flow_velocity = ( 2 * 60 * 60 * aquifer_permeability * saline_density * grav_accel / saline_viscosity @@ -416,7 +421,7 @@ def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 # diameter - well_radius = 0.5 * self.wellbore_diameter # m, radius is half of diameter + well_radius = 0.5 * self.well_casing_size # m, radius is half of diameter cloggingVel = 0.3 # meter/year membraneFilterIndex = 0.1 # s/l2 diff --git a/testdata/test_ates.esdl b/testdata/test_ates.esdl index af292619..fdce87e1 100644 --- a/testdata/test_ates.esdl +++ b/testdata/test_ates.esdl @@ -1,5 +1,5 @@ - + @@ -15,59 +15,59 @@ - + - + - + - + - + - + - - + + - + - - + + - + - - + + - - + + - + @@ -75,10 +75,10 @@ - - + + - + @@ -89,37 +89,37 @@ - - + + - - + + - - + + - + - - + + - + - - + + - + @@ -127,10 +127,10 @@ - - + + - + @@ -141,8 +141,8 @@ - - + + @@ -150,8 +150,8 @@ - - + + @@ -161,13 +161,13 @@ - - + + - - + + @@ -177,8 +177,8 @@ - - + + @@ -186,39 +186,39 @@ - - + + - - + + - - + + - - + + - + - + - + @@ -236,8 +236,8 @@ - - + + diff --git a/unit_test/entities/test_ates_cluster.py b/unit_test/entities/test_ates_cluster.py index 0e038381..c8005d8e 100644 --- a/unit_test/entities/test_ates_cluster.py +++ b/unit_test/entities/test_ates_cluster.py @@ -41,6 +41,7 @@ def setUp(self) -> None: self.salinity = ATES_DEFAULTS.salinity self.well_casing_size = ATES_DEFAULTS.well_casing_size self.well_distance = ATES_DEFAULTS.well_distance + maximum_flowrate = ATES_DEFAULTS.maximum_flowrate # Create a production cluster object self.ates_cluster = AtesCluster( asset_name="ates_cluster", @@ -55,6 +56,8 @@ def setUp(self) -> None: aquifer_anisotropy=self.aquifer_anisotropy, salinity=self.salinity, well_distance=self.well_distance, + well_casing_size=self.well_casing_size, + maximum_flowrate=maximum_flowrate, ) def test_injection_ates(self) -> None: From 7b94b5a091a4c040981d0328a756f2b0ad939f81 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 6 Feb 2026 16:27:30 +0100 Subject: [PATCH 09/10] fix lint, test, typecheck --- .../adapter/transforms/esdl_asset_mappers/ates_mapper.py | 2 +- src/omotes_simulator_core/entities/assets/ates_cluster.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py index 51fe4d9e..2615f992 100644 --- a/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py +++ b/src/omotes_simulator_core/adapter/transforms/esdl_asset_mappers/ates_mapper.py @@ -73,7 +73,7 @@ def to_entity(self, esdl_asset: EsdlAssetObject) -> AssetAbstract: esdl_property_name="wellCasingSize", default_value=ATES_DEFAULTS.well_casing_size ), maximum_flowrate=esdl_asset.get_property( - esdl_property_name="maximumFlowRate", default_value=ATES_DEFAULTS.maximum_flow_rate + esdl_property_name="maximumFlowRate", default_value=ATES_DEFAULTS.maximum_flowrate ), ) diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index 17b767a3..bef7a6bd 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -21,7 +21,6 @@ from omotes_simulator_core.entities.assets.asset_abstract import AssetAbstract from omotes_simulator_core.entities.assets.asset_defaults import ( - ATES_DEFAULTS, DEFAULT_TEMPERATURE, DEFAULT_TEMPERATURE_DIFFERENCE, PROPERTY_HEAT_DEMAND, From ca2b8f49d06396ce4673dd8503b07689e1e76cc5 Mon Sep 17 00:00:00 2001 From: Ryvo Octaviano Date: Fri, 6 Feb 2026 16:32:19 +0100 Subject: [PATCH 10/10] put gravity acceleration in default --- .../entities/assets/asset_defaults.py | 1 + .../entities/assets/ates_cluster.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/omotes_simulator_core/entities/assets/asset_defaults.py b/src/omotes_simulator_core/entities/assets/asset_defaults.py index a9fe1d7b..b60a038f 100644 --- a/src/omotes_simulator_core/entities/assets/asset_defaults.py +++ b/src/omotes_simulator_core/entities/assets/asset_defaults.py @@ -28,6 +28,7 @@ DEFAULT_POWER = 500000.0 # [W] DEFAULT_MISSING_VALUE = -9999.99 # [-] DEFAULT_ROUGHNESS = 1e-3 # [m] +DEFAULT_GRAVITY_ACCELERATION = 9.81 # m/s2 @dataclass diff --git a/src/omotes_simulator_core/entities/assets/ates_cluster.py b/src/omotes_simulator_core/entities/assets/ates_cluster.py index bef7a6bd..78503038 100644 --- a/src/omotes_simulator_core/entities/assets/ates_cluster.py +++ b/src/omotes_simulator_core/entities/assets/ates_cluster.py @@ -21,6 +21,7 @@ from omotes_simulator_core.entities.assets.asset_abstract import AssetAbstract from omotes_simulator_core.entities.assets.asset_defaults import ( + DEFAULT_GRAVITY_ACCELERATION, DEFAULT_TEMPERATURE, DEFAULT_TEMPERATURE_DIFFERENCE, PROPERTY_HEAT_DEMAND, @@ -388,7 +389,6 @@ def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: reference equation: NVOE Richtlijnen Ondergrondse Energieopslag, november 2006 parameters value: https://www.thermogis.nl/doublet-en-economische-parameters-hto """ - grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 @@ -396,7 +396,13 @@ def _get_max_flowrate_extraction_norm(self, P: float, T: float) -> float: well_radius = 0.5 * self.well_casing_size # m max_extract_flow_velocity = ( - 2 * 60 * 60 * aquifer_permeability * saline_density * grav_accel / saline_viscosity + 2 + * 60 + * 60 + * aquifer_permeability + * saline_density + * DEFAULT_GRAVITY_ACCELERATION + / saline_viscosity ) # m3/h max_flowrate = ( @@ -415,7 +421,6 @@ def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: reference equation: NVOE Richtlijnen Ondergrondse Energieopslag, november 2006 parameters value: https://www.thermogis.nl/doublet-en-economische-parameters-hto """ - grav_accel = 9.81 # m/s2 saline_density = self._get_saline_density(P, T) saline_viscosity = self._get_saline_viscosity(P, T) aquifer_permeability = self.aquifer_permeability * 9.8692326671601e-16 # mD to m2 @@ -428,7 +433,12 @@ def _get_max_flowrate_injection_norm(self, P: float, T: float) -> float: max_infiltrate_flow_velocity = ( 1000 * math.pow( - 576 * aquifer_permeability * saline_density * grav_accel / saline_viscosity, 0.6 + 576 + * aquifer_permeability + * saline_density + * DEFAULT_GRAVITY_ACCELERATION + / saline_viscosity, + 0.6, ) * math.sqrt(cloggingVel / (2 * membraneFilterIndex * equivLoadHoursPerYear)) )