Loading src/pythermogis/workflow/utc/cooling_temp.py 0 → 100644 +56 −0 Original line number Diff line number Diff line from pythermogis.workflow.utc.flow import calculate_volumetric_flow def calculate_cooling_temperature( props, input, drawdown_pressure: float, well_distance: float, injection_temp: float ) -> float: results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=well_distance, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) cooling_frac_min = 0.0 cooling_frac_max = 0.5 cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) for _ in range(1000): if abs(cooling_frac_max - cooling_frac_min) <= 0.001: break cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) lifetime = calc_lifetime( well_distance=well_distance, thickness=input.thickness * input.ntg, delta_temp_fraction=cooling_frac, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) if lifetime < props.optim_dist_lifetime: cooling_frac_min = cooling_frac else: cooling_frac_max = cooling_frac if (not props.is_ates) and cooling_frac > 0.3: raise RuntimeError( f"Large cooling factor ({cooling_frac}) could result in less reliable calculation" ) return cooling_frac * (input.temperature - injection_temp) src/pythermogis/workflow/utc/doublet.py +3 −1 Original line number Diff line number Diff line from dataclasses import dataclass from pythermogis.workflow.utc.utc_properties import UTCConfiguration from pythermogis.workflow.utc.doublet_utils import optimize_well_distance, calculate_injection_temp_with_heat_pump, calculate_cooling_temperature, \ from pythermogis.workflow.utc.doublet_utils import calculate_injection_temp_with_heat_pump, \ calc_lifetime from pythermogis.workflow.utc.pressure import calculate_max_pressure, optimize_pressure from pythermogis.workflow.utc.cooling_temp import calculate_cooling_temperature from pythermogis.workflow.utc.well_distance import optimize_well_distance EUR_PER_CT_PER_KWH = 0.36 NPV_SCALE = 1e-6 Loading src/pythermogis/workflow/utc/doublet_utils.py +0 −111 Original line number Diff line number Diff line import math from numba import njit from pythermogis.workflow.utc.flow import calculate_volumetric_flow from pythermogis.workflow.utc.water import density, heat_capacity, get_salinity, get_hydrostatic_pressure @njit Loading Loading @@ -61,61 +60,6 @@ def get_cop_carnot(eta: float, Tout: float, Tin: float) -> float: return eta * (Tcond + TKELVIN) / (Tcond - Tevap) def calculate_cooling_temperature( props, input, drawdown_pressure: float, well_distance: float, injection_temp: float ) -> float: results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=well_distance, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) cooling_frac_min = 0.0 cooling_frac_max = 0.5 cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) for _ in range(1000): if abs(cooling_frac_max - cooling_frac_min) <= 0.001: break cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) lifetime = calc_lifetime( well_distance=well_distance, thickness=input.thickness * input.ntg, delta_temp_fraction=cooling_frac, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) if lifetime < props.optim_dist_lifetime: cooling_frac_min = cooling_frac else: cooling_frac_max = cooling_frac if (not props.is_ates) and cooling_frac > 0.3: raise RuntimeError( f"Large cooling factor ({cooling_frac}) could result in less reliable calculation" ) return cooling_frac * (input.temperature - injection_temp) def calc_lifetime( well_distance: float, thickness: float, Loading Loading @@ -169,61 +113,6 @@ def calc_lifetime( return Eseg / Eflowseg def optimize_well_distance( props, input, drawdown_pressure: float, injection_temp: float, ) -> float: dist_min = props.optim_dist_well_dist_min dist_max = props.optim_dist_well_dist_max dist = 0.5 * (dist_min + dist_max) for iter_count in range(1000): if abs(dist_max - dist_min) <= 10.0: break dist = 0.5 * (dist_min + dist_max) # --- Compute flow for this distance --- results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=dist, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) # --- Compute lifetime for this distance --- lifetime = calc_lifetime( well_distance=dist, thickness=input.thickness * input.ntg, delta_temp_fraction=props.optim_dist_cooling_fraction, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) # --- Bisection rule --- if lifetime < props.optim_dist_lifetime: dist_min = dist else: dist_max = dist # If no convergence in 1000 iterations else: print(f"WARNING: Well distance optimization failed to converge. Final dist={dist}") return dist def get_along_hole_length( true_vertical_depth: float, Loading src/pythermogis/workflow/utc/doubletcalc.py +13 −16 Original line number Diff line number Diff line import math from typing import TYPE_CHECKING from dataclasses import dataclass from pythermogis.workflow.utc.utc_properties import UTCConfiguration Loading @@ -7,8 +6,6 @@ from pydoubletcalc import Aquifer, Doublet, Well, WellPipeSegment from pythermogis.workflow.utc.water import get_salinity from pythermogis.workflow.utc.rock import get_geothermal_gradient if TYPE_CHECKING: from pythermogis.workflow.utc.doublet import DoubletInput INCH_SI = 0.0254 Loading @@ -23,7 +20,7 @@ class Doublet1DResults: def doubletcalc( props: UTCConfiguration, input: DoubletInput, input, drawdown_pressure: float, well_distance: float, injection_temp: float, Loading @@ -45,12 +42,12 @@ def doubletcalc( injector = Well( aquifer=aquifer, well_type="injector", pipe_segments=WellPipeSegment( pipe_segments=[WellPipeSegment( ah_depth=input.depth, tv_depth=get_along_hole_length(input.depth, well_distance, props.well_curv_scaling, props.max_tvd_stepout_factor), inner_diameter=props.inner_diameter * INCH_SI, rougness=props.roughness * 1e-3 * INCH_SI ), roughness=props.roughness * 1e-3 * INCH_SI )], aquifer_top_depth=input.depth, pipe_scaling=0, target_segment_length=props.segment_length, Loading @@ -67,12 +64,12 @@ def doubletcalc( producer = Well( aquifer=aquifer, well_type="producer", pipe_segments=WellPipeSegment( pipe_segments=[WellPipeSegment( ah_depth=input.depth, tv_depth=get_along_hole_length(input.depth, well_distance, props.well_curv_scaling, props.max_tvd_stepout_factor), inner_diameter=props.inner_diameter * INCH_SI, rougness=props.roughness * 1e-3 * INCH_SI ), roughness=props.roughness * 1e-3 * INCH_SI )], aquifer_top_depth=input.depth, pipe_scaling=0, target_segment_length=props.segment_length, Loading Loading @@ -109,23 +106,23 @@ def doubletcalc( ) def get_total_skin_injection(props, input): if (not props.use_stimulation()) or (input.transmissivity_with_ntg() > props.stim_kh_max()): if (not props.use_stimulation) or (input.transmissivity_with_ntg > props.stim_kh_max): stim_add_skin_inj = 0.0 else: stim_add_skin_inj = props.stim_add_skin_inj() return props.skin_injector() + stim_add_skin_inj return props.skin_injector + stim_add_skin_inj def get_total_skin_production(props, input): if (not props.use_stimulation()) or (input.transmissivity_with_ntg() > props.stim_kh_max()): if (not props.use_stimulation) or (input.transmissivity_with_ntg > props.stim_kh_max): stim_add_skin_prod = 0.0 else: stim_add_skin_prod = props.stim_add_skin_prod() stim_add_skin_prod = props.stim_add_skin_prod return props.skin_producer() + stim_add_skin_prod return props.skin_producer + stim_add_skin_prod def get_pump_production_depth(props, depth: float) -> float: return min(props.pump_depth(), depth / 2) return min(props.pump_depth, depth / 2) def get_along_hole_length( true_vertical_depth: float, Loading src/pythermogis/workflow/utc/well_distance.py 0 → 100644 +58 −0 Original line number Diff line number Diff line from pythermogis.workflow.utc.doublet_utils import calc_lifetime from pythermogis.workflow.utc.flow import calculate_volumetric_flow def optimize_well_distance( props, input, drawdown_pressure: float, injection_temp: float, ) -> float: dist_min = props.optim_dist_well_dist_min dist_max = props.optim_dist_well_dist_max dist = 0.5 * (dist_min + dist_max) for iter_count in range(1000): if abs(dist_max - dist_min) <= 10.0: break dist = 0.5 * (dist_min + dist_max) # --- Compute flow for this distance --- results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=dist, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) # --- Compute lifetime for this distance --- lifetime = calc_lifetime( well_distance=dist, thickness=input.thickness * input.ntg, delta_temp_fraction=props.optim_dist_cooling_fraction, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) # --- Bisection rule --- if lifetime < props.optim_dist_lifetime: dist_min = dist else: dist_max = dist # If no convergence in 1000 iterations else: print(f"WARNING: Well distance optimization failed to converge. Final dist={dist}") return dist Loading
src/pythermogis/workflow/utc/cooling_temp.py 0 → 100644 +56 −0 Original line number Diff line number Diff line from pythermogis.workflow.utc.flow import calculate_volumetric_flow def calculate_cooling_temperature( props, input, drawdown_pressure: float, well_distance: float, injection_temp: float ) -> float: results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=well_distance, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) cooling_frac_min = 0.0 cooling_frac_max = 0.5 cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) for _ in range(1000): if abs(cooling_frac_max - cooling_frac_min) <= 0.001: break cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) lifetime = calc_lifetime( well_distance=well_distance, thickness=input.thickness * input.ntg, delta_temp_fraction=cooling_frac, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) if lifetime < props.optim_dist_lifetime: cooling_frac_min = cooling_frac else: cooling_frac_max = cooling_frac if (not props.is_ates) and cooling_frac > 0.3: raise RuntimeError( f"Large cooling factor ({cooling_frac}) could result in less reliable calculation" ) return cooling_frac * (input.temperature - injection_temp)
src/pythermogis/workflow/utc/doublet.py +3 −1 Original line number Diff line number Diff line from dataclasses import dataclass from pythermogis.workflow.utc.utc_properties import UTCConfiguration from pythermogis.workflow.utc.doublet_utils import optimize_well_distance, calculate_injection_temp_with_heat_pump, calculate_cooling_temperature, \ from pythermogis.workflow.utc.doublet_utils import calculate_injection_temp_with_heat_pump, \ calc_lifetime from pythermogis.workflow.utc.pressure import calculate_max_pressure, optimize_pressure from pythermogis.workflow.utc.cooling_temp import calculate_cooling_temperature from pythermogis.workflow.utc.well_distance import optimize_well_distance EUR_PER_CT_PER_KWH = 0.36 NPV_SCALE = 1e-6 Loading
src/pythermogis/workflow/utc/doublet_utils.py +0 −111 Original line number Diff line number Diff line import math from numba import njit from pythermogis.workflow.utc.flow import calculate_volumetric_flow from pythermogis.workflow.utc.water import density, heat_capacity, get_salinity, get_hydrostatic_pressure @njit Loading Loading @@ -61,61 +60,6 @@ def get_cop_carnot(eta: float, Tout: float, Tin: float) -> float: return eta * (Tcond + TKELVIN) / (Tcond - Tevap) def calculate_cooling_temperature( props, input, drawdown_pressure: float, well_distance: float, injection_temp: float ) -> float: results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=well_distance, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) cooling_frac_min = 0.0 cooling_frac_max = 0.5 cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) for _ in range(1000): if abs(cooling_frac_max - cooling_frac_min) <= 0.001: break cooling_frac = 0.5 * (cooling_frac_min + cooling_frac_max) lifetime = calc_lifetime( well_distance=well_distance, thickness=input.thickness * input.ntg, delta_temp_fraction=cooling_frac, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) if lifetime < props.optim_dist_lifetime: cooling_frac_min = cooling_frac else: cooling_frac_max = cooling_frac if (not props.is_ates) and cooling_frac > 0.3: raise RuntimeError( f"Large cooling factor ({cooling_frac}) could result in less reliable calculation" ) return cooling_frac * (input.temperature - injection_temp) def calc_lifetime( well_distance: float, thickness: float, Loading Loading @@ -169,61 +113,6 @@ def calc_lifetime( return Eseg / Eflowseg def optimize_well_distance( props, input, drawdown_pressure: float, injection_temp: float, ) -> float: dist_min = props.optim_dist_well_dist_min dist_max = props.optim_dist_well_dist_max dist = 0.5 * (dist_min + dist_max) for iter_count in range(1000): if abs(dist_max - dist_min) <= 10.0: break dist = 0.5 * (dist_min + dist_max) # --- Compute flow for this distance --- results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=dist, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) # --- Compute lifetime for this distance --- lifetime = calc_lifetime( well_distance=dist, thickness=input.thickness * input.ntg, delta_temp_fraction=props.optim_dist_cooling_fraction, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) # --- Bisection rule --- if lifetime < props.optim_dist_lifetime: dist_min = dist else: dist_max = dist # If no convergence in 1000 iterations else: print(f"WARNING: Well distance optimization failed to converge. Final dist={dist}") return dist def get_along_hole_length( true_vertical_depth: float, Loading
src/pythermogis/workflow/utc/doubletcalc.py +13 −16 Original line number Diff line number Diff line import math from typing import TYPE_CHECKING from dataclasses import dataclass from pythermogis.workflow.utc.utc_properties import UTCConfiguration Loading @@ -7,8 +6,6 @@ from pydoubletcalc import Aquifer, Doublet, Well, WellPipeSegment from pythermogis.workflow.utc.water import get_salinity from pythermogis.workflow.utc.rock import get_geothermal_gradient if TYPE_CHECKING: from pythermogis.workflow.utc.doublet import DoubletInput INCH_SI = 0.0254 Loading @@ -23,7 +20,7 @@ class Doublet1DResults: def doubletcalc( props: UTCConfiguration, input: DoubletInput, input, drawdown_pressure: float, well_distance: float, injection_temp: float, Loading @@ -45,12 +42,12 @@ def doubletcalc( injector = Well( aquifer=aquifer, well_type="injector", pipe_segments=WellPipeSegment( pipe_segments=[WellPipeSegment( ah_depth=input.depth, tv_depth=get_along_hole_length(input.depth, well_distance, props.well_curv_scaling, props.max_tvd_stepout_factor), inner_diameter=props.inner_diameter * INCH_SI, rougness=props.roughness * 1e-3 * INCH_SI ), roughness=props.roughness * 1e-3 * INCH_SI )], aquifer_top_depth=input.depth, pipe_scaling=0, target_segment_length=props.segment_length, Loading @@ -67,12 +64,12 @@ def doubletcalc( producer = Well( aquifer=aquifer, well_type="producer", pipe_segments=WellPipeSegment( pipe_segments=[WellPipeSegment( ah_depth=input.depth, tv_depth=get_along_hole_length(input.depth, well_distance, props.well_curv_scaling, props.max_tvd_stepout_factor), inner_diameter=props.inner_diameter * INCH_SI, rougness=props.roughness * 1e-3 * INCH_SI ), roughness=props.roughness * 1e-3 * INCH_SI )], aquifer_top_depth=input.depth, pipe_scaling=0, target_segment_length=props.segment_length, Loading Loading @@ -109,23 +106,23 @@ def doubletcalc( ) def get_total_skin_injection(props, input): if (not props.use_stimulation()) or (input.transmissivity_with_ntg() > props.stim_kh_max()): if (not props.use_stimulation) or (input.transmissivity_with_ntg > props.stim_kh_max): stim_add_skin_inj = 0.0 else: stim_add_skin_inj = props.stim_add_skin_inj() return props.skin_injector() + stim_add_skin_inj return props.skin_injector + stim_add_skin_inj def get_total_skin_production(props, input): if (not props.use_stimulation()) or (input.transmissivity_with_ntg() > props.stim_kh_max()): if (not props.use_stimulation) or (input.transmissivity_with_ntg > props.stim_kh_max): stim_add_skin_prod = 0.0 else: stim_add_skin_prod = props.stim_add_skin_prod() stim_add_skin_prod = props.stim_add_skin_prod return props.skin_producer() + stim_add_skin_prod return props.skin_producer + stim_add_skin_prod def get_pump_production_depth(props, depth: float) -> float: return min(props.pump_depth(), depth / 2) return min(props.pump_depth, depth / 2) def get_along_hole_length( true_vertical_depth: float, Loading
src/pythermogis/workflow/utc/well_distance.py 0 → 100644 +58 −0 Original line number Diff line number Diff line from pythermogis.workflow.utc.doublet_utils import calc_lifetime from pythermogis.workflow.utc.flow import calculate_volumetric_flow def optimize_well_distance( props, input, drawdown_pressure: float, injection_temp: float, ) -> float: dist_min = props.optim_dist_well_dist_min dist_max = props.optim_dist_well_dist_max dist = 0.5 * (dist_min + dist_max) for iter_count in range(1000): if abs(dist_max - dist_min) <= 10.0: break dist = 0.5 * (dist_min + dist_max) # --- Compute flow for this distance --- results = calculate_volumetric_flow( props=props, input_data=input, original_pressure=drawdown_pressure, well_distance=dist, injection_temp=injection_temp, ) flowrate = min(results.flowrate, props.max_flow) # --- Compute lifetime for this distance --- lifetime = calc_lifetime( well_distance=dist, thickness=input.thickness * input.ntg, delta_temp_fraction=props.optim_dist_cooling_fraction, porosity=input.porosity, flowrate=flowrate, depth=input.depth, reservoir_temp=input.temperature, salinity_surface=props.salinity_surface, salinity_gradient=props.salinity_gradient, cp_rock=props.optim_dist_cp_rock, rho_rock=props.optim_dist_rho_rock, ) # --- Bisection rule --- if lifetime < props.optim_dist_lifetime: dist_min = dist else: dist_max = dist # If no convergence in 1000 iterations else: print(f"WARNING: Well distance optimization failed to converge. Final dist={dist}") return dist