From 843524b7abbcc714dbeb48777d6536d307fc4d48 Mon Sep 17 00:00:00 2001 From: knappersfy Date: Tue, 9 Dec 2025 13:36:17 +0100 Subject: [PATCH 1/2] addded typehints to all utc code --- pixi.lock | 4 +- pyproject.toml | 1 + src/pythermogis/workflow/utc/cooling_temp.py | 14 +- src/pythermogis/workflow/utc/doubletcalc.py | 19 ++- src/pythermogis/workflow/utc/economics.py | 129 +++++++++++------- src/pythermogis/workflow/utc/flow.py | 13 +- src/pythermogis/workflow/utc/heatpump.py | 13 +- src/pythermogis/workflow/utc/pressure.py | 47 ++++--- src/pythermogis/workflow/utc/water.py | 2 - src/pythermogis/workflow/utc/well_distance.py | 22 ++- tests/utc/test_doublet.py | 46 +++++++ 11 files changed, 219 insertions(+), 91 deletions(-) diff --git a/pixi.lock b/pixi.lock index fcf6dea..e0f720c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -4766,8 +4766,8 @@ packages: timestamp: 1733195786147 - pypi: C:/Users/knappersfy/work/thermogis/pydoubletcalc name: pydoubletcalc - version: 0.0.2 - sha256: 89fbb20be0f5da6d0e8e543b9160e4ac3aef8369557ca71ae4a96086f521145e + version: 0.0.3 + sha256: a391e31a4d31cad94ef6af7520fb9418f2c7f1741fa28384734097c9fda1b1fd requires_dist: - pandas - matplotlib diff --git a/pyproject.toml b/pyproject.toml index b0c22a3..2eb8841 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ platforms = ["win-64", "linux-64"] [tool.pixi.pypi-dependencies] pythermogis = { path = ".", editable = true } +pydoubletcalc = { path = 'C:\Users\knappersfy\work\thermogis\pydoubletcalc', editable = true } [tool.pixi.tasks] test = "PYTHONPATH=src/pythermogis pytest -s tests/" diff --git a/src/pythermogis/workflow/utc/cooling_temp.py b/src/pythermogis/workflow/utc/cooling_temp.py index 7d279ff..cca4291 100644 --- a/src/pythermogis/workflow/utc/cooling_temp.py +++ b/src/pythermogis/workflow/utc/cooling_temp.py @@ -1,9 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + from pythermogis.workflow.utc.doublet_utils import calc_lifetime from pythermogis.workflow.utc.flow import calculate_volumetric_flow +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + def calculate_cooling_temperature( - props, input, drawdown_pressure: float, well_distance: float, injection_temp: float + props: UTCConfiguration, + input: DoubletInput, + drawdown_pressure: float, + well_distance: float, + injection_temp: float, ) -> float: results = calculate_volumetric_flow( props=props, diff --git a/src/pythermogis/workflow/utc/doubletcalc.py b/src/pythermogis/workflow/utc/doubletcalc.py index 6e1194c..cf9f7c2 100644 --- a/src/pythermogis/workflow/utc/doubletcalc.py +++ b/src/pythermogis/workflow/utc/doubletcalc.py @@ -1,13 +1,20 @@ +from __future__ import annotations + import math from dataclasses import dataclass +from typing import TYPE_CHECKING from numba import njit from pydoubletcalc import Aquifer, Doublet, Well, WellPipeSegment from pythermogis.workflow.utc.rock import get_geothermal_gradient -from pythermogis.workflow.utc.utc_properties import UTCConfiguration from pythermogis.workflow.utc.water import get_salinity +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + + INCH_SI = 0.0254 @@ -23,7 +30,7 @@ class Doublet1DResults: def doubletcalc( props: UTCConfiguration, - input, + input: DoubletInput, drawdown_pressure: float, well_distance: float, injection_temp: float, @@ -137,18 +144,18 @@ def doubletcalc( ) -def get_total_skin_injection(props, input): +def get_total_skin_injection(props: UTCConfiguration, input: DoubletInput) -> float: 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() + stim_add_skin_inj = props.stim_add_skin_inj return props.skin_injector + stim_add_skin_inj -def get_total_skin_production(props, input): +def get_total_skin_production(props: UTCConfiguration, input: DoubletInput): if (not props.use_stimulation) or ( input.transmissivity_with_ntg > props.stim_kh_max ): @@ -159,7 +166,7 @@ def get_total_skin_production(props, input): return props.skin_producer + stim_add_skin_prod -def get_pump_production_depth(props, depth: float) -> float: +def get_pump_production_depth(props: UTCConfiguration, depth: float) -> float: return min(props.pump_depth, depth / 2) diff --git a/src/pythermogis/workflow/utc/economics.py b/src/pythermogis/workflow/utc/economics.py index 82e9514..17d82c9 100644 --- a/src/pythermogis/workflow/utc/economics.py +++ b/src/pythermogis/workflow/utc/economics.py @@ -1,9 +1,17 @@ +from __future__ import annotations + from dataclasses import dataclass +from typing import TYPE_CHECKING import numpy as np +from numpy.typing import NDArray from pythermogis.workflow.utc.doublet_utils import get_along_hole_length +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + SECONDS_IN_YEAR = 365 * 24 * 3600 MJ_TO_GJ = 1e-3 KWH_TO_MJ = 0.36 * 1e9 @@ -33,8 +41,8 @@ class EconomicsResults: def calculate_economics( - props, - input, + props: UTCConfiguration, + input: DoubletInput, well_distance: float, heat_power_produced: list[float], stimulation_capex: float, @@ -81,7 +89,7 @@ def calculate_economics( def calculate_capex( - props, + props: UTCConfiguration, heat_power_produced: list[float], stimulation_capex: float, hp_cop: float, @@ -147,7 +155,7 @@ def calculate_capex( ) -def shift_time_series(series: list[float], shift: int): +def shift_time_series(series: list[float], shift: int) -> None: n = len(series) if shift <= 0 or shift >= n: for i in range(n): @@ -158,7 +166,9 @@ def shift_time_series(series: list[float], shift: int): series[i] = 0 -def calculate_capex_for_wells(props, ah_length_array, stimulation_capex): +def calculate_capex_for_wells( + props: UTCConfiguration, ah_length_array: list[float], stimulation_capex: float +) -> float: inj_depth = ah_length_array[0] prod_depth = ah_length_array[0] @@ -170,13 +180,15 @@ def calculate_capex_for_wells(props, ah_length_array, stimulation_capex): return capex * props.well_cost_scaling -def calculate_well_cost(props, depth): +def calculate_well_cost(props: UTCConfiguration, depth: float) -> float: return ( props.well_cost_z2 * depth**2 + props.well_cost_z * depth ) * 1e-6 + props.well_cost_const -def allocate_capex_for_wells(props, total_capex_ts, capex_well): +def allocate_capex_for_wells( + props: UTCConfiguration, total_capex_ts: NDArray[np.float64], capex_well: float +): drilling_years = props.drilling_time yearly_cost = capex_well / max(drilling_years, 1) @@ -184,23 +196,32 @@ def allocate_capex_for_wells(props, total_capex_ts, capex_well): total_capex_ts[y] += yearly_cost -def get_max_hp_added_power(initial, hp_power_years): +def get_max_hp_added_power(initial: float, hp_power_years: list[float] | None) -> float: if hp_power_years is not None: return max(hp_power_years) return initial -def calculate_hp_added_power_after_heat_pump(props, hp_added_power, hp_cop): +def calculate_hp_added_power_after_heat_pump( + props: UTCConfiguration, hp_added_power: float, hp_cop: float +) -> float: alt_price = props.hp_alternative_heating_price return hp_added_power if alt_price < 0 else hp_added_power * (1 + 1 / (hp_cop - 1)) -def calculate_installation_mw(heat_power_produced, hp_added_power): +def calculate_installation_mw( + heat_power_produced: list[float], hp_added_power: float +) -> float: last_val = heat_power_produced[-1] return max(last_val - hp_added_power, 0) -def calculate_total_capex_1year(props, total_capex_ts, hp_after_hp, installation_mw): +def calculate_total_capex_1year( + props: UTCConfiguration, + total_capex_ts: NDArray[np.float64], + hp_after_hp: float, + installation_mw: float, +) -> float: last_year = max(props.drilling_time - 1, 0) total_capex_ts[last_year] += ( @@ -214,21 +235,21 @@ def calculate_total_capex_1year(props, total_capex_ts, hp_after_hp, installation def process_annual_data( - props, - heat_power_produced, - total_capex_ts, - heat_power_per_year, - variable_opex, - fixed_opex, - total_opex_ts, - total_capex_1year, - installation_mw, - hp_after_hp, - hp_cop, - hp_cop_years, - season_factor_years, - pump_power_required, -): + props: UTCConfiguration, + heat_power_produced: list[float], + total_capex_ts: NDArray[np.float64], + heat_power_per_year: NDArray[np.float64], + variable_opex: NDArray[np.float64], + fixed_opex: NDArray[np.float64], + total_opex_ts: NDArray[np.float64], + total_capex_1year: float, + installation_mw: float, + hp_after_hp: float, + hp_cop: float, + hp_cop_years: float, + season_factor_years: float, + pump_power_required: float, +) -> float: sum_capex = 0.0 for year in range(props.economic_lifetime): @@ -281,16 +302,16 @@ def process_annual_data( def calculate_variable_opex( - props, - season_factor, - elec_price, - pump_power, - heat_power_produced, - heat_exchanger_parasitic, - heat_power_gj_per_year, - hp_cop, - hp_after_hp, -): + props: UTCConfiguration, + season_factor: float, + elec_price: float, + pump_power: float, + heat_power_produced: float, + heat_exchanger_parasitic: float, + heat_power_gj_per_year: float, + hp_cop: float, + hp_after_hp: float, +) -> float: pump_cost = ( -(pump_power * 1e3) * SECONDS_IN_YEAR @@ -333,7 +354,12 @@ def calculate_variable_opex( return pump_cost + parasitic_cost + heat_pump_cost + opex_per_energy -def calculate_fixed_opex(props, hp_after_hp, installation_mw, total_capex): +def calculate_fixed_opex( + props: UTCConfiguration, + hp_after_hp: float, + installation_mw: float, + total_capex: float, +) -> float: return ( -props.opex_base / 1e6 - (props.hp_opex * hp_after_hp + props.opex_per_power * installation_mw) * 1e-3 @@ -341,15 +367,15 @@ def calculate_fixed_opex(props, hp_after_hp, installation_mw, total_capex): ) -def get_heat_exchanger_season_factor(props): +def get_heat_exchanger_season_factor(props: UTCConfiguration) -> float: return props.load_hours / HOURS_IN_YEAR def calculate_utc( - props, - heat_power_per_year, - total_opex_ts, - total_capex, + props: UTCConfiguration, + heat_power_per_year: float, + total_opex_ts: float, + total_capex: float, ) -> UTCCalculatorResults: net_cash_worth_eia = calculate_net_cash_worth_eia(props, total_capex) present_value = total_capex * props.debt_equity - net_cash_worth_eia @@ -393,13 +419,13 @@ def calculate_utc( ) -def calculate_net_cash_worth_eia(props, total_capex): +def calculate_net_cash_worth_eia(props: UTCConfiguration, total_capex: float) -> float: tax_rate = props.tax_rate project_interest_rate = calculate_project_interest_rate(props) return (tax_rate * (props.ecn_eia * total_capex)) / (1 + project_interest_rate) -def calculate_payment_value(props, present_value): +def calculate_payment_value(props: UTCConfiguration, present_value: float) -> float: if props.interest_loan == 0: return 0.0 @@ -407,11 +433,13 @@ def calculate_payment_value(props, present_value): return (props.interest_loan / (factor - 1)) * (-(present_value * factor)) -def calculate_depreciation_cost(props, total_capex): +def calculate_depreciation_cost(props: UTCConfiguration, total_capex: float) -> float: return -(total_capex / props.economic_lifetime) -def calculate_future_value(present_value, payment, interest, year): +def calculate_future_value( + present_value: float, payment: float, interest: float, year: float +) -> float: future_value = present_value * ((1 + interest) ** (year - 1)) if payment != 0: future_value += payment * (((1 + interest) ** (year - 1)) - 1) / interest @@ -419,8 +447,11 @@ def calculate_future_value(present_value, payment, interest, year): def calculate_unit_technical_cost( - props, total_capex, discounted_income, discounted_heat_produced -): + props: UTCConfiguration, + total_capex: float, + discounted_income: float, + discounted_heat_produced: float, +) -> float: if discounted_heat_produced <= 0: return 0.0 return ( @@ -429,7 +460,7 @@ def calculate_unit_technical_cost( ) * 1e6 -def calculate_project_interest_rate(props): +def calculate_project_interest_rate(props: UTCConfiguration) -> float: return (1 - props.tax_rate) * props.interest_loan * props.debt_equity + ( 1 - props.debt_equity ) * props.equity_return diff --git a/src/pythermogis/workflow/utc/flow.py b/src/pythermogis/workflow/utc/flow.py index fafc33e..6af7099 100644 --- a/src/pythermogis/workflow/utc/flow.py +++ b/src/pythermogis/workflow/utc/flow.py @@ -1,4 +1,7 @@ +from __future__ import annotations + from dataclasses import dataclass +from typing import TYPE_CHECKING import numpy as np @@ -6,6 +9,10 @@ from pythermogis.workflow.utc.doublet_utils import get_orc_efficiency from pythermogis.workflow.utc.doubletcalc import doubletcalc from pythermogis.workflow.utc.heatpump import calculate_heat_pump_performance +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + @dataclass class VolumetricFlowResults: @@ -21,12 +28,12 @@ class VolumetricFlowResults: def calculate_volumetric_flow( - props, - input_data, + props: UTCConfiguration, + input_data: DoubletInput, original_pressure: float, well_distance: float, injection_temp: float, -): +) -> VolumetricFlowResults: for step in [0, 1e5, -1e5, 2e5, -2e5, 3e5, -3e5]: d1d_results = doubletcalc( props, input_data, original_pressure + step, well_distance, injection_temp diff --git a/src/pythermogis/workflow/utc/heatpump.py b/src/pythermogis/workflow/utc/heatpump.py index 1f31362..37ce497 100644 --- a/src/pythermogis/workflow/utc/heatpump.py +++ b/src/pythermogis/workflow/utc/heatpump.py @@ -1,4 +1,7 @@ +from __future__ import annotations + from dataclasses import dataclass +from typing import TYPE_CHECKING from pythermogis.workflow.utc.doublet_utils import get_cop_carnot from pythermogis.workflow.utc.water import ( @@ -8,6 +11,10 @@ from pythermogis.workflow.utc.water import ( heat_capacity, ) +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + @dataclass class HeatPumpPerformanceResults: @@ -17,8 +24,8 @@ class HeatPumpPerformanceResults: def calculate_heat_pump_performance( - props, - input_data, + props: UTCConfiguration, + input_data: DoubletInput, production_temp: float, injection_temp: float, flowrate: float, @@ -68,7 +75,7 @@ def calculate_heat_pump_performance( def calculate_heat_pump_start_temp( - props, production_temp: float, injection_temp: float + props: UTCConfiguration, production_temp: float, injection_temp: float ) -> float: """ Python version of calculateHeatPumpStartTemp(...) diff --git a/src/pythermogis/workflow/utc/pressure.py b/src/pythermogis/workflow/utc/pressure.py index 3e5ee06..54a495d 100644 --- a/src/pythermogis/workflow/utc/pressure.py +++ b/src/pythermogis/workflow/utc/pressure.py @@ -1,12 +1,19 @@ +from __future__ import annotations + from dataclasses import dataclass +from typing import TYPE_CHECKING from pythermogis.workflow.utc.economics import calculate_economics from pythermogis.workflow.utc.flow import calculate_volumetric_flow +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + def calculate_max_pressure( - props, - input_data, + props: UTCConfiguration, + input_data: DoubletInput, use_olsthoorn_max_pressure: bool, well_distance: float, injection_temp: float, @@ -86,13 +93,13 @@ class PressureOptimizerResults: def optimize_pressure( - props, - input, + props: UTCConfiguration, + input: DoubletInput, drawdown_pressure: float, well_distance: float, injection_temp: float, stimulation_capex: float, -): +) -> PressureOptimizerResults: pres_tol = 1e-4 * drawdown_pressure pres_step = 1e1 * pres_tol @@ -239,14 +246,14 @@ def optimize_pressure( def utc_slope( - props, - input, - pres, - pres_step, - well_distance, - injection_temp, - stimulation_capex, -): + props: UTCConfiguration, + input: DoubletInput, + pres: float, + pres_step: float, + well_distance: float, + injection_temp: float, + stimulation_capex: float, +) -> float: utc1 = find_utc_for_pres( props, input, @@ -267,13 +274,13 @@ def utc_slope( def find_utc_for_pres( - props, - input, - pres, - well_distance, - injection_temp, - stimulation_capex, -): + props: UTCConfiguration, + input: DoubletInput, + pres: float, + well_distance: float, + injection_temp: float, + stimulation_capex: float, +) -> float: flow_results = calculate_volumetric_flow( props=props, input_data=input, diff --git a/src/pythermogis/workflow/utc/water.py b/src/pythermogis/workflow/utc/water.py index cf16e4f..82b5be2 100644 --- a/src/pythermogis/workflow/utc/water.py +++ b/src/pythermogis/workflow/utc/water.py @@ -90,5 +90,3 @@ def heat_capacity( ) return heat_capacity_val * 1e3 - - diff --git a/src/pythermogis/workflow/utc/well_distance.py b/src/pythermogis/workflow/utc/well_distance.py index 1f09a68..4a58527 100644 --- a/src/pythermogis/workflow/utc/well_distance.py +++ b/src/pythermogis/workflow/utc/well_distance.py @@ -1,13 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np from scipy.optimize import brentq from pythermogis.workflow.utc.doublet_utils import calc_lifetime from pythermogis.workflow.utc.flow import calculate_volumetric_flow +if TYPE_CHECKING: + from pythermogis.workflow.utc.doublet import DoubletInput + from pythermogis.workflow.utc.utc_properties import UTCConfiguration + def optimize_well_distance_original( - props, - input, + props: UTCConfiguration, + input: DoubletInput, drawdown_pressure: float, injection_temp: float, ) -> float: @@ -77,7 +85,11 @@ def optimize_well_distance_original( def f1( - well_distance, props, input, drawdown_pressure: float, injection_temp: float + well_distance: float, + props: UTCConfiguration, + input: DoubletInput, + drawdown_pressure: float, + injection_temp: float, ) -> float: # --- Compute flow for this distance --- results = calculate_volumetric_flow( @@ -108,8 +120,8 @@ def f1( def optimize_well_distance( - props, - input, + props: UTCConfiguration, + input: DoubletInput, drawdown_pressure: float, injection_temp: float, ) -> float: diff --git a/tests/utc/test_doublet.py b/tests/utc/test_doublet.py index eb053e6..e42d93a 100644 --- a/tests/utc/test_doublet.py +++ b/tests/utc/test_doublet.py @@ -93,4 +93,50 @@ def test_calculate_doublet_performance_approximate(): assert np.isclose(result.var_opex, -7.510325908660889, rtol=rtol) assert np.isclose(result.fixed_opex, -11.227973937988281, rtol=rtol) +def test_calculate_doublet_performance(): + # 85ish it/s + props = UTCConfiguration( + viscosity_mode="kestin", + dh_return_temp=40, + segment_length=1.0, + ) + warmup_input = DoubletInput( + unknown_input_value=-999.0, + thickness=100.0, + transmissivity=17500.0, + transmissivity_with_ntg=0.0, + ntg=1.0, + depth=2000.0, + porosity=0.0, + temperature=76.0, + ) + _ = calculate_doublet_performance(props, warmup_input) + + start = timeit.default_timer() + n_sims = 1000 + + for i in range(n_sims): + transmissivity = 17500.0 + (i % 10) * 5 + temp = 76.0 + (i % 5) * 0.1 + depth = 2000.0 + (i % 4) * 2 + + input_data = DoubletInput( + unknown_input_value=-999.0, + thickness=100.0, + transmissivity=transmissivity, + transmissivity_with_ntg=0.0, + ntg=1.0, + depth=depth, + porosity=0.0, + temperature=temp, + ) + + _ = calculate_doublet_performance(props, input_data) + + time_elapsed = timeit.default_timer() - start + + print( + f"{n_sims} simulations took: {time_elapsed:.1f} seconds\n" + f"{n_sims/time_elapsed:.1f} simulations per second" + ) -- GitLab From a488abc7b86470120a2cffd32e392e0a081a4601 Mon Sep 17 00:00:00 2001 From: knappersfy Date: Tue, 9 Dec 2025 13:37:52 +0100 Subject: [PATCH 2/2] rm pydoubletcalc --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2eb8841..b0c22a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,6 @@ platforms = ["win-64", "linux-64"] [tool.pixi.pypi-dependencies] pythermogis = { path = ".", editable = true } -pydoubletcalc = { path = 'C:\Users\knappersfy\work\thermogis\pydoubletcalc', editable = true } [tool.pixi.tasks] test = "PYTHONPATH=src/pythermogis pytest -s tests/" -- GitLab