From d8378c4ea825fbb96e0210f579f90d3f11760339 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Mon, 17 Oct 2022 09:32:45 +0200 Subject: [PATCH 01/11] enum implementation --- pygfunction/gfunction.py | 94 +++++++-------- tests/gfunction_test.py | 249 ++++++++++++++++++++------------------- 2 files changed, 166 insertions(+), 177 deletions(-) diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index 99e1d8ad..73127e4b 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np +from numpy.typing import NDArray from scipy.cluster.hierarchy import cut_tree, dendrogram, linkage from scipy.constants import pi from scipy.interpolate import interp1d as interp1d @@ -16,6 +17,15 @@ from .utilities import _initialize_figure, _format_axes from . import utilities +from enum import Enum, auto +from typing import Dict, List, Callable, Union, Optional + + +class Method(Enum): + similarities = auto() + detailed = auto() + equivalent = auto() + class gFunction(object): """ @@ -202,40 +212,26 @@ class gFunction(object): Transfer, 127, 105496. """ - def __init__(self, boreholes_or_network, alpha, time=None, - method='equivalent', boundary_condition=None, options={}): + def __init__(self, boreholes_or_network, alpha: float, time: Optional[Union[float, NDArray[np.float64]]] = None, + method: Optional[Method] = None, boundary_condition: Optional[str] = None, options: Optional[dict] = None): self.alpha = alpha self.time = time - self.method = method + self.method = method if method is not None else Method.equivalent self.boundary_condition = boundary_condition - self.options = options + self.options = options if options is not None else {} # Format inputs and assign default values where needed self._format_inputs(boreholes_or_network) # Check the validity of inputs self._check_inputs() - # Load the chosen solver - if self.method.lower()=='similarities': - self.solver = _Similarities( - self.boreholes, self.network, self.time, - self.boundary_condition, **self.options) - elif self.method.lower()=='detailed': - self.solver = _Detailed( - self.boreholes, self.network, self.time, - self.boundary_condition, **self.options) - elif self.method.lower()=='equivalent': - self.solver = _Equivalent( - self.boreholes, self.network, self.time, - self.boundary_condition, **self.options) - else: - raise ValueError(f"'{method}' is not a valid method.") + self.solver = solver_mathing[self.method](self.boreholes, self.network, self.time, self.boundary_condition, **self.options) # If a time vector is provided, evaluate the g-function if self.time is not None: self.gFunc = self.evaluate_g_function(self.time) - def evaluate_g_function(self, time): + def evaluate_g_function(self, time: Union[float, NDArray[np.float64]]): """ Evaluate the g-function. @@ -864,7 +860,7 @@ def _format_inputs(self, boreholes_or_network): self.boundary_condition = 'UBWT' # If the 'equivalent' solver is selected for the 'MIFT' condition, # switch to the 'similarities' solver if boreholes are in series - if self.boundary_condition == 'MIFT' and self.method.lower() == 'equivalent': + if self.boundary_condition == 'MIFT' and self.method is Method.equivalent: if not np.all(np.array(self.network.c, dtype=int) == -1): warnings.warn( "\nSolver 'equivalent' is only valid for " @@ -919,10 +915,9 @@ def _check_inputs(self): assert type(self.boundary_condition) is str and self.boundary_condition in acceptable_boundary_conditions, \ f"Boundary condition '{self.boundary_condition}' is not an acceptable boundary condition. \n" \ f"Please provide one of the following inputs : {acceptable_boundary_conditions}" - acceptable_methods = ['detailed', 'similarities', 'equivalent'] - assert type(self.method) is str and self.method in acceptable_methods, \ + assert isinstance(self.method, Method), \ f"Method '{self.method}' is not an acceptable method. \n" \ - f"Please provide one of the following inputs : {acceptable_methods}" + f"Please provide one of the following inputs : {[method for method in Method]}" return @@ -1002,10 +997,7 @@ def uniform_heat_extraction(boreholes, time, alpha, use_similarities=True, 'dtype':dtype, 'disp':disp} # Select the correct solver: - if use_similarities: - method='similarities' - else: - method='detailed' + method=Method.similarities if use_similarities else Method.detailed # Evaluate g-function gFunc = gFunction( boreholes, alpha, time=time, method=method, @@ -1428,13 +1420,13 @@ class _BaseSolver(object): Default is numpy.double. """ - def __init__(self, boreholes, network, time, boundary_condition, - nSegments=8, segment_ratios=utilities.segment_ratios, - approximate_FLS=False, mQuad=11, nFLS=10, disp=False, - profiles=False, kind='linear', dtype=np.double, + def __init__(self, boreholes: list, network: Network, time: Union[float, NDArray[np.float64]], boundary_condition: str, + nSegments: int = 8, segment_ratios: Optional[Union[NDArray[np.float64], Callable]]=utilities.segment_ratios, + approximate_FLS:bool=False, mQuad: int=11, nFLS: int=10, disp:bool=False, + profiles:bool=False, kind:str='linear', dtype:float=np.double, **other_options): - self.boreholes = boreholes - self.network = network + self.boreholes: list = boreholes + self.network: Network = network # Convert time to a 1d array self.time = np.atleast_1d(time).flatten() self.boundary_condition = boundary_condition @@ -1446,10 +1438,10 @@ def __init__(self, boreholes, network, time, boundary_condition, self.nBoreSegments = nSegments if isinstance(segment_ratios, np.ndarray): segment_ratios = [segment_ratios] * nBoreholes - elif segment_ratios is None: - segment_ratios = [np.full(n, 1./n) for n in self.nBoreSegments] elif callable(segment_ratios): segment_ratios = [segment_ratios(n) for n in self.nBoreSegments] + else: + segment_ratios = [np.full(n, 1./n) for n in self.nBoreSegments] self.segment_ratios = segment_ratios # Shortcut for segment_ratios comparisons self._equal_segment_ratios = \ @@ -1462,10 +1454,8 @@ def __init__(self, boreholes, network, time, boundary_condition, rtol=1e-6) for segment_ratios in self.segment_ratios] # Find indices of first and last segments along boreholes - self._i0Segments = [sum(self.nBoreSegments[0:i]) - for i in range(nBoreholes)] - self._i1Segments = [sum(self.nBoreSegments[0:(i + 1)]) - for i in range(nBoreholes)] + self._i0Segments = [sum(self.nBoreSegments[0:i]) for i in range(nBoreholes)] + self._i1Segments = [sum(self.nBoreSegments[0:(i + 1)]) for i in range(nBoreholes)] self.approximate_FLS = approximate_FLS self.mQuad = mQuad self.nFLS = nFLS @@ -1476,11 +1466,11 @@ def __init__(self, boreholes, network, time, boundary_condition, # Check the validity of inputs self._check_inputs() # Initialize the solver with solver-specific options - self.nSources = self.initialize(**other_options) + self.nSources = self.initialize(*other_options) return - def initialize(self, *kwargs): + def initialize(self, *kwargs) -> int: """ Perform any calculation required at the initialization of the solver and returns the number of finite line heat sources in the borefield. @@ -1502,7 +1492,6 @@ def initialize(self, *kwargs): 'return the number of finite line heat sources in the borefield ' 'used to initialize the matrix of segment-to-segment thermal ' 'response factors (of size: nSources x nSources)') - return None def solve(self, time, alpha): """ @@ -1696,8 +1685,7 @@ def borehole_segments(self): """ boreSegments = [] # list for storage of boreSegments for b, nSegments, segment_ratios in zip(self.boreholes, self.nBoreSegments, self.segment_ratios): - segments = b.segments(nSegments, segment_ratios=segment_ratios) - boreSegments.extend(segments) + boreSegments.extend(b.segments(nSegments, segment_ratios=segment_ratios)) return boreSegments @@ -1945,8 +1933,7 @@ def initialize(self, **kwargs): """ # Split boreholes into segments self.boreSegments = self.borehole_segments() - nSources = len(self.boreSegments) - return nSources + return len(self.boreSegments) def thermal_response_factors(self, time, alpha, kind='linear'): """ @@ -2027,7 +2014,8 @@ def thermal_response_factors(self, time, alpha, kind='linear'): h_ij = interp1d(np.hstack((0., time)), h_ij, kind=kind, copy=True, axis=2) toc = perf_counter() - if self.disp: print(f' {toc - tic:.3f} sec') + if self.disp: + print(f' {toc - tic:.3f} sec') return h_ij @@ -2092,7 +2080,6 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): return h, i_segment, j_segment - class _Similarities(_BaseSolver): """ Similarities solver for the evaluation of the g-function. @@ -4091,7 +4078,7 @@ def _map_axial_segment_pairs(self, iBor, jBor, elif reaSource: # Find segment pairs for the real FLS solution compare_pairs = self._compare_real_pairs - elif imgSource: + else: # Find segment pairs for the image FLS solution compare_pairs = self._compare_image_pairs # Dive both boreholes into segments @@ -4176,8 +4163,9 @@ def _check_solver_specific_inputs(self): self.network.m_flow_network[0]*len(self.network.b) # Verify that all boreholes have the same piping configuration # This is best done by comparing the matrix of thermal resistances. - assert np.all( - [np.allclose(self.network.p[0]._Rd, pipe._Rd) - for pipe in self.network.p]), \ + assert np.all([np.allclose(self.network.p[0]._Rd, pipe._Rd) for pipe in self.network.p]), \ "All boreholes must have the same piping configuration." return + + +solver_mathing: Dict[Method, Callable] = {Method.similarities: _Similarities, Method.detailed: _Detailed, Method.equivalent: _Equivalent} diff --git a/tests/gfunction_test.py b/tests/gfunction_test.py index 2cfab47f..f8297131 100644 --- a/tests/gfunction_test.py +++ b/tests/gfunction_test.py @@ -5,6 +5,7 @@ import pytest import pygfunction as gt +from pygfunction.gfunction import Method # ============================================================================= @@ -14,53 +15,53 @@ # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole', 'equivalent', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', 'equivalent', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', Method.equivalent, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', Method.equivalent, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'equivalent' solver - uniform segments - ('single_borehole', 'equivalent', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', 'equivalent', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', Method.equivalent, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', Method.equivalent, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole', 'equivalent', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', 'equivalent', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments_approx', np.array([10.8993464, 17.09872924, 19.0794071])), + ('single_borehole', Method.equivalent, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', Method.equivalent, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([10.8993464, 17.09872924, 19.0794071])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole', 'equivalent', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', 'equivalent', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments_approx', np.array([10.96117468, 17.2450427, 19.25351959])), + ('single_borehole', Method.equivalent, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', Method.equivalent, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([10.96117468, 17.2450427, 19.25351959])), # 'similarities' solver - unequal segments - ('single_borehole', 'similarities', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', 'similarities', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', 'similarities', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', Method.similarities, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', Method.similarities, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'similarities' solver - uniform segments - ('single_borehole', 'similarities', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', 'similarities', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', 'similarities', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', Method.similarities, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', Method.similarities, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole', 'similarities', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', 'similarities', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', 'similarities', 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), + ('single_borehole', Method.similarities, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', Method.similarities, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole', 'similarities', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', 'similarities', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', 'similarities', 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), + ('single_borehole', Method.similarities, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', Method.similarities, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), # 'detailed' solver - unequal segments - ('single_borehole', 'detailed', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', 'detailed', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', 'detailed', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', Method.detailed, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', Method.detailed, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'detailed' solver - uniform segments - ('single_borehole', 'detailed', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', 'detailed', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', 'detailed', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', Method.detailed, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', Method.detailed, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole', 'detailed', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', 'detailed', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', 'detailed', 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), + ('single_borehole', Method.detailed, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', Method.detailed, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole', 'detailed', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', 'detailed', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', 'detailed', 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), + ('single_borehole', Method.detailed, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', Method.detailed, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), ]) def test_gfunctions_UBWT(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -85,53 +86,53 @@ def test_gfunctions_UBWT(field, method, opts, expected, request): # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole', 'equivalent', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'equivalent', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.equivalent, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.equivalent, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'equivalent' solver - uniform segments - ('single_borehole', 'equivalent', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'equivalent', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.equivalent, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.equivalent, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole', 'equivalent', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'equivalent', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), + ('single_borehole', Method.equivalent, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.equivalent, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole', 'equivalent', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'equivalent', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), + ('single_borehole', Method.equivalent, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.equivalent, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), # 'similarities' solver - unequal segments - ('single_borehole', 'similarities', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'similarities', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'similarities', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.similarities, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.similarities, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'similarities' solver - uniform segments - ('single_borehole', 'similarities', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'similarities', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'similarities', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.similarities, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.similarities, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole', 'similarities', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'similarities', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'similarities', 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', Method.similarities, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.similarities, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole', 'similarities', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'similarities', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'similarities', 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', Method.similarities, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.similarities, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'detailed' solver - unequal segments - ('single_borehole', 'detailed', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'detailed', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'detailed', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.detailed, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.detailed, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'detailed' solver - uniform segments - ('single_borehole', 'detailed', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', 'detailed', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', 'detailed', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', Method.detailed, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', Method.detailed, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole', 'detailed', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'detailed', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'detailed', 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', Method.detailed, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.detailed, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole', 'detailed', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', 'detailed', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', 'detailed', 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', Method.detailed, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', Method.detailed, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), ]) def test_gfunctions_UHTR(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -157,61 +158,61 @@ def test_gfunctions_UHTR(field, method, opts, expected, request): # The 'equivalent' solver is not applied to series-connected boreholes. @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole_network', 'equivalent', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', 'equivalent', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', 'equivalent', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('single_borehole_network', Method.equivalent, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', Method.equivalent, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', Method.equivalent, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), # 'equivalent' solver - uniform segments - ('single_borehole_network', 'equivalent', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', 'equivalent', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', 'equivalent', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('single_borehole_network', Method.equivalent, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', Method.equivalent, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', Method.equivalent, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole_network', 'equivalent', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', 'equivalent', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', 'equivalent', 'unequal_segments_approx', np.array([12.66228879, 18.57863253, 20.33526092])), + ('single_borehole_network', Method.equivalent, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', Method.equivalent, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([12.66228879, 18.57863253, 20.33526092])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole_network', 'equivalent', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', 'equivalent', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', 'equivalent', 'uniform_segments_approx', np.array([12.93153466, 18.88937176, 20.63801163])), + ('single_borehole_network', Method.equivalent, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', Method.equivalent, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([12.93153466, 18.88937176, 20.63801163])), # 'similarities' solver - unequal segments - ('single_borehole_network', 'similarities', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', 'similarities', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', 'similarities', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), - ('ten_boreholes_network_rectangular_series', 'similarities', 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), + ('single_borehole_network', Method.similarities, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', Method.similarities, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', Method.similarities, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('ten_boreholes_network_rectangular_series', Method.similarities, 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), # 'similarities' solver - uniform segments - ('single_borehole_network', 'similarities', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', 'similarities', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', 'similarities', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), - ('ten_boreholes_network_rectangular_series', 'similarities', 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), + ('single_borehole_network', Method.similarities, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', Method.similarities, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', Method.similarities, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('ten_boreholes_network_rectangular_series', Method.similarities, 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole_network', 'similarities', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', 'similarities', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', 'similarities', 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), - ('ten_boreholes_network_rectangular_series', 'similarities', 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), + ('single_borehole_network', Method.similarities, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', Method.similarities, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', Method.similarities, 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), + ('ten_boreholes_network_rectangular_series', Method.similarities, 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole_network', 'similarities', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', 'similarities', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', 'similarities', 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), - ('ten_boreholes_network_rectangular_series', 'similarities', 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), + ('single_borehole_network', Method.similarities, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', Method.similarities, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', Method.similarities, 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), + ('ten_boreholes_network_rectangular_series', Method.similarities, 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), # 'detailed' solver - unequal segments - ('single_borehole_network', 'detailed', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', 'detailed', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', 'detailed', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), - ('ten_boreholes_network_rectangular_series', 'detailed', 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), + ('single_borehole_network', Method.detailed, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', Method.detailed, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', Method.detailed, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('ten_boreholes_network_rectangular_series', Method.detailed, 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), # 'detailed' solver - uniform segments - ('single_borehole_network', 'detailed', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', 'detailed', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', 'detailed', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), - ('ten_boreholes_network_rectangular_series', 'detailed', 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), + ('single_borehole_network', Method.detailed, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', Method.detailed, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', Method.detailed, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('ten_boreholes_network_rectangular_series', Method.detailed, 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole_network', 'detailed', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', 'detailed', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', 'detailed', 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), - ('ten_boreholes_network_rectangular_series', 'detailed', 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), + ('single_borehole_network', Method.detailed, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', Method.detailed, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', Method.detailed, 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), + ('ten_boreholes_network_rectangular_series', Method.detailed, 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole_network', 'detailed', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', 'detailed', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', 'detailed', 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), - ('ten_boreholes_network_rectangular_series', 'detailed', 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), + ('single_borehole_network', Method.detailed, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', Method.detailed, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', Method.detailed, 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), + ('ten_boreholes_network_rectangular_series', Method.detailed, 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), ]) def test_gfunctions_MIFT(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -239,21 +240,21 @@ def test_gfunctions_MIFT(field, method, opts, expected, request): # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("method, opts, expected", [ # 'similarities' solver - unequal segments - ('similarities', 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), + (Method.similarities, 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), # 'similarities' solver - uniform segments - ('similarities', 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), + (Method.similarities, 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), # 'similarities' solver - unequal segments, FLS approximation - ('similarities', 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), + (Method.similarities, 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), # 'similarities' solver - uniform segments, FLS approximation - ('similarities', 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), + (Method.similarities, 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), # 'detailed' solver - unequal segments - ('detailed', 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), + (Method.detailed, 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), # 'detailed' solver - uniform segments - ('detailed', 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), + (Method.detailed, 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), # 'detailed' solver - unequal segments, FLS approximation - ('detailed', 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), + (Method.detailed, 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), # 'detailed' solver - uniform segments, FLS approximation - ('detailed', 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), + (Method.detailed, 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), ]) def test_gfunctions_UBWT(two_boreholes_inclined, method, opts, expected, request): # Extract the bore field from the fixture From 008efe35653a3db565705d5c3f6d4f7028adf007 Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:00:38 +0200 Subject: [PATCH 02/11] update boreholes --- README.md | 5 +- examples/regular_bore_field.py | 4 +- pygfunction/boreholes.py | 556 ++++++++++++++------------------- pygfunction/gfunction.py | 183 ++++++----- pygfunction/heat_transfer.py | 16 +- pygfunction/networks.py | 4 +- pygfunction/pipes.py | 12 +- pygfunction/utilities.py | 2 +- tests/boreholes_test.py | 28 +- tests/gfunction_test.py | 8 +- tests/heat_transfer_test.py | 14 +- 11 files changed, 376 insertions(+), 456 deletions(-) diff --git a/README.md b/README.md index 8b8faaf7..353c1cde 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ total): ```python import pygfunction as gt import numpy as np -time = np.array([(i+1)*3600. for i in range(24)]) # Calculate hourly for one day -boreField = gt.boreholes.rectangle_field(N_1=10, N_2=10, B_1=7.5, B_2=7.5, H=150., D=4., r_b=0.075) + +time = np.array([(i + 1) * 3600. for i in range(24)]) # Calculate hourly for one day +boreField = gt.boreholes.rectangle_field(n_1=10, n_2=10, b_1=7.5, b_2=7.5, h=150., d=4., r_b=0.075) gFunc = gt.gfunction.gFunction(boreField, alpha=1.0e-6, time=time) gFunc.visualize_g_function() ``` diff --git a/examples/regular_bore_field.py b/examples/regular_bore_field.py index aea2e78c..3fb9799a 100644 --- a/examples/regular_bore_field.py +++ b/examples/regular_bore_field.py @@ -33,10 +33,10 @@ def main(): boxField = gt.boreholes.box_shaped_field(N_1, N_2, B, B, H, D, r_b) # U-shaped field of 4 x 3 boreholes - UField = gt.boreholes.U_shaped_field(N_1, N_2, B, B, H, D, r_b) + UField = gt.boreholes.u_shaped_field(N_1, N_2, B, B, H, D, r_b) # L-shaped field of 4 x 3 boreholes - LField = gt.boreholes.L_shaped_field(N_1, N_2, B, B, H, D, r_b) + LField = gt.boreholes.l_shaped_field(N_1, N_2, B, B, H, D, r_b) # Circular field of 8 boreholes circleField = gt.boreholes.circle_field(N_b, R, H, D, r_b) diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 4cc98f89..84f3bb03 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -1,21 +1,25 @@ # -*- coding: utf-8 -*- +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np +from numpy.typing import NDArray from scipy.constants import pi from scipy.spatial.distance import pdist +from typing import Tuple, List, Union, Optional from .utilities import _initialize_figure, _format_axes, _format_axes_3d -class Borehole(object): +class Borehole: """ Contains information regarding the dimensions and position of a borehole. Attributes ---------- - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -29,26 +33,27 @@ class Borehole(object): Direction (in radians) of the tilt of the borehole. """ - def __init__(self, H, D, r_b, x, y, tilt=0., orientation=0.): - self.H = float(H) # Borehole length - self.D = float(D) # Borehole buried depth - self.r_b = float(r_b) # Borehole radius - self.x = float(x) # Borehole x coordinate position - self.y = float(y) # Borehole y coordinate position + + def __init__(self, h: float, d: float, r_b: float, x: float, y: float, tilt: float = 0., orientation: Optional[float] = None): + self.h: float = float(h) # Borehole length + self.d: float = float(d) # Borehole buried depth + self.r_b: float = float(r_b) # Borehole radius + self.x: float = float(x) # Borehole x coordinate position + self.y: float = float(y) # Borehole y coordinate position # Borehole inclination - self.tilt = float(tilt) + self.tilt: float = float(tilt) # Borehole orientation - self.orientation = float(orientation) + self.orientation: float = float(orientation) if orientation is not None else 0. # Check if borehole is inclined - self._is_tilted = np.abs(self.tilt) > 1.0e-6 + self._is_tilted: bool = np.abs(self.tilt) > 1.0e-6 def __repr__(self): - s = (f'Borehole(H={self.H}, D={self.D}, r_b={self.r_b}, x={self.x},' + s = (f'Borehole(H={self.h}, D={self.d}, r_b={self.r_b}, x={self.x},' f' y={self.y}, tilt={self.tilt},' f' orientation={self.orientation})') return s - def distance(self, target): + def distance(self, target: Borehole) -> float: """ Evaluate the distance between the current borehole and a target borehole. @@ -70,17 +75,15 @@ def distance(self, target): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1.distance(b2) 5.0 """ - dis = max(self.r_b, - np.sqrt((self.x - target.x)**2 + (self.y - target.y)**2)) - return dis + return max(self.r_b, np.sqrt((self.x - target.x) ** 2 + (self.y - target.y) ** 2)) - def is_tilted(self): + def is_tilted(self) -> bool: """ Returns true if the borehole is inclined. @@ -92,7 +95,7 @@ def is_tilted(self): """ return self._is_tilted - def is_vertical(self): + def is_vertical(self) -> bool: """ Returns true if the borehole is vertical. @@ -104,7 +107,7 @@ def is_vertical(self): """ return not self._is_tilted - def position(self): + def position(self) -> Tuple[float, float]: """ Returns the position of the borehole. @@ -115,21 +118,20 @@ def position(self): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1.position() (5.0, 0.0) """ - pos = (self.x, self.y) - return pos + return self.x, self.y - def segments(self, nSegments, segment_ratios=None): + def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> List[Borehole]: """ Split a borehole into segments. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -145,37 +147,37 @@ def segments(self, nSegments, segment_ratios=None): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1.segments(5) """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] - boreSegments = [] + segment_ratios = np.full(n_segments, 1. / n_segments) + z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] + bore_segments = [] for z_i, ratios in zip(z, segment_ratios): # Divide borehole into segments of equal length - H = ratios * self.H + H = ratios * self.h # Buried depth of the i-th segment - D = self.D + z_i * np.cos(self.tilt) + D = self.d + z_i * np.cos(self.tilt) # x-position x = self.x + z_i * np.sin(self.tilt) * np.cos(self.orientation) # y-position y = self.y + z_i * np.sin(self.tilt) * np.sin(self.orientation) # Add to list of segments - boreSegments.append( + bore_segments.append( Borehole(H, D, self.r_b, x, y, tilt=self.tilt, orientation=self.orientation)) - return boreSegments + return bore_segments - def _segment_edges(self, nSegments, segment_ratios=None): + def _segment_edges(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> NDArray[np.float64]: """ Linear coordinates of the segment edges. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -193,22 +195,22 @@ def _segment_edges(self, nSegments, segment_ratios=None): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1._segment_edges(5) """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.H + segment_ratios = np.full(n_segments, 1. / n_segments) + z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.h return z - def _segment_midpoints(self, nSegments, segment_ratios=None): + def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> NDArray[np.float64]: """ Linear coordinates of the segment midpoints. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -226,18 +228,18 @@ def _segment_midpoints(self, nSegments, segment_ratios=None): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1._segment_midpoints(5) """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] \ - + segment_ratios * self.H / 2 + segment_ratios = np.full(n_segments, 1. / n_segments) + z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] \ + + segment_ratios * self.h / 2 return z -class _EquivalentBorehole(object): +class _EquivalentBorehole: """ Contains information regarding the dimensions and position of an equivalent borehole. @@ -281,18 +283,19 @@ class _EquivalentBorehole(object): Simulation, 14 (4), 446-460. """ - def __init__(self, boreholes): + + def __init__(self, boreholes: Union[List[Borehole], List[_EquivalentBorehole], tuple]): if isinstance(boreholes[0], Borehole): - self.H = boreholes[0].H - self.D = boreholes[0].D - self.r_b = boreholes[0].r_b - self.x = np.array([b.x for b in boreholes]) - self.y = np.array([b.y for b in boreholes]) - self.tilt = boreholes[0].tilt + self.h: float = boreholes[0].h + self.d: float = boreholes[0].d + self.r_b: float = boreholes[0].r_b + self.x: NDArray[np.float64] = np.array([b.x for b in boreholes]) + self.y: NDArray[np.float64] = np.array([b.y for b in boreholes]) + self.tilt: float = boreholes[0].tilt self.orientation = np.array([b.orientation for b in boreholes]) elif isinstance(boreholes[0], _EquivalentBorehole): - self.H = boreholes[0].H - self.D = boreholes[0].D + self.h = boreholes[0].h + self.d = boreholes[0].d self.r_b = boreholes[0].r_b self.x = np.concatenate([b.x for b in boreholes]) self.y = np.concatenate([b.y for b in boreholes]) @@ -300,17 +303,17 @@ def __init__(self, boreholes): self.orientation = np.concatenate( [b.orientation for b in boreholes]) elif type(boreholes) is tuple: - self.H, self.D, self.r_b, self.x, self.y = boreholes[:5] + self.h, self.d, self.r_b, self.x, self.y = boreholes[:5] self.x = np.atleast_1d(self.x) self.y = np.atleast_1d(self.y) - if len(boreholes)==7: + if len(boreholes) == 7: self.tilt, self.orientation = boreholes[5:] - self.nBoreholes = len(self.x) + self.nBoreholes: int = len(self.x) # Check if borehole is inclined - self._is_tilted = np.abs(self.tilt) > 1.0e-6 + self._is_tilted: bool = np.abs(self.tilt) > 1.0e-6 - def distance(self, target): + def distance(self, target: _EquivalentBorehole) -> NDArray[np.float64]: """ Evaluate the distance between the current borehole and a target borehole. @@ -338,13 +341,9 @@ def distance(self, target): array([[ 5., 7.07106781, 11.18033989]]) """ - dis = np.maximum( - np.sqrt( - np.add.outer(target.x, -self.x)**2 + np.add.outer(target.y, -self.y)**2), - self.r_b) - return dis + return np.maximum(np.sqrt(np.add.outer(target.x, -self.x) ** 2 + np.add.outer(target.y, -self.y) ** 2), self.r_b) - def is_tilted(self): + def is_tilted(self) -> bool: """ Returns true if the borehole is inclined. @@ -356,7 +355,7 @@ def is_tilted(self): """ return self._is_tilted - def is_vertical(self): + def is_vertical(self) -> bool: """ Returns true if the borehole is vertical. @@ -368,7 +367,7 @@ def is_vertical(self): """ return not self._is_tilted - def position(self): + def position(self) -> Tuple[NDArray[np.float64], NDArray[np.float64]]: """ Returns the position of the boreholes represented by the equivalent borehole. @@ -385,15 +384,15 @@ def position(self): (array([ 0., 5., 10.]), array([0., 0., 0.])) """ - return (self.x, self.y) + return self.x, self.y - def segments(self, nSegments, segment_ratios=None): + def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> List[_EquivalentBorehole]: """ Split an equivalent borehole into segments. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -413,20 +412,20 @@ def segments(self, nSegments, segment_ratios=None): """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] + segment_ratios = np.full(n_segments, 1. / n_segments) + z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] segments = [_EquivalentBorehole( - (ratios * self.H, - self.D + z_i * np.cos(self.tilt), + (ratios * self.h, + self.d + z_i * np.cos(self.tilt), self.r_b, self.x + z_i * np.sin(self.tilt) * np.cos(self.orientation), self.y + z_i * np.sin(self.tilt) * np.sin(self.orientation), self.tilt, self.orientation) - ) for z_i, ratios in zip(z, segment_ratios)] + ) for z_i, ratios in zip(z, segment_ratios)] return segments - def unique_distance(self, target, disTol=0.01): + def unique_distance(self, target: _EquivalentBorehole, dis_tol: float = 0.01) -> Tuple[NDArray[np.float64], NDArray[np.float64]]: """ Find unique distances between pairs of boreholes for a pair of equivalent boreholes. @@ -435,7 +434,7 @@ def unique_distance(self, target, disTol=0.01): ---------- target : _EquivalentBorehole object Target borehole for which the distances are evaluated. - disTol : float, optional + dis_tol : float, optional Relative tolerance on radial distance. Two distances (d1, d2) between two pairs of boreholes are considered equal if the difference between the two distances (abs(d1-d2)) is below tolerance. @@ -463,38 +462,38 @@ def unique_distance(self, target, disTol=0.01): """ # Find all distances between the boreholes, sorted and flattened all_dis = np.sort(self.distance(target).flatten()) - nDis = len(all_dis) + n_dis = len(all_dis) # Find unique distances within tolerance - dis = [] - wDis = [] + dis: List[float] = [] + w_dis: List[float] = [] # Start the search at the first distance j0 = 0 j1 = 1 - while j0 < nDis and j1 > 0: + while j0 < n_dis and j1 > 0: # Find the index of the first distance for which the distance is # outside tolerance to the current distance - j1 = np.argmax(all_dis >= (1 + disTol) * all_dis[j0]) + j1 = np.argmax(all_dis >= (1 + dis_tol) * all_dis[j0]) if j1 > j0: # Add the average of the distances within tolerance to the # list of unique distances and store the number of distances - dis.append(np.mean(all_dis[j0:j1])) - wDis.append(j1-j0) + dis.append(np.mean(all_dis[j0:j1])[0]) + w_dis.append(j1 - j0) else: # All remaining distances are within tolerance - dis.append(np.mean(all_dis[j0:])) - wDis.append(nDis-j0) + dis.append(np.mean(all_dis[j0:])[0]) + w_dis.append(n_dis - j0) j0 = j1 - - return np.array(dis), np.array(wDis) - def _segment_edges(self, nSegments, segment_ratios=None): + return np.array(dis), np.array(w_dis) + + def _segment_edges(self, n_segments: int, segment_ratios: Optional[np.float64] = None) -> float: """ Linear coordinates of the segment edges. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -512,22 +511,21 @@ def _segment_edges(self, nSegments, segment_ratios=None): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1._segment_edges(5) """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.H - return z + segment_ratios = np.full(n_segments, 1. / n_segments) + return np.concatenate(([0.], np.cumsum(segment_ratios))) * self.h - def _segment_midpoints(self, nSegments, segment_ratios=None): + def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[np.float64] = None) -> float: """ Linear coordinates of the segment midpoints. Parameters ---------- - nSegments : int + n_segments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -545,18 +543,16 @@ def _segment_midpoints(self, nSegments, segment_ratios=None): Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> b1._segment_midpoints(5) """ if segment_ratios is None: - segment_ratios = np.full(nSegments, 1. / nSegments) - z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] \ - + segment_ratios * self.H / 2 - return z + segment_ratios = np.full(n_segments, 1. / n_segments) + return self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] + segment_ratios * self.h / 2 -def find_duplicates(boreField, disp=False): +def find_duplicates(bore_field: List[Borehole], disp: bool = False): """ The distance method :func:`Borehole.distance` is utilized to find all duplicate boreholes in a boreField. @@ -567,7 +563,7 @@ def find_duplicates(boreField, disp=False): Parameters ---------- - boreField : list + bore_field : list A list of :class:`Borehole` objects disp : bool, optional Set to true to print progression messages. @@ -579,11 +575,11 @@ def find_duplicates(boreField, disp=False): A list of tuples where the tuples are pairs of duplicates """ # Number of boreholes - n = len(boreField) + n = len(bore_field) # Max. borehole radius - r_b = np.max([b.r_b for b in boreField]) + r_b = np.max([b.r_b for b in bore_field]) # Array of coordinates - coordinates = np.array([[b.x, b.y] for b in boreField]) + coordinates = np.array([[b.x, b.y] for b in bore_field]) # Find distance between each pair of boreholes distances = pdist(coordinates, 'euclidean') # Find duplicate boreholes @@ -599,7 +595,7 @@ def find_duplicates(boreField, disp=False): return duplicate_pairs -def remove_duplicates(boreField, disp=False): +def remove_duplicates(bore_field: List[Borehole], disp=False): """ Removes all of the duplicates found from the duplicate pairs returned in :func:`check_duplicates`. @@ -609,7 +605,7 @@ def remove_duplicates(boreField, disp=False): Parameters ---------- - boreField : list + bore_field : list A list of :class:`Borehole` objects disp : bool, optional Set to true to print progression messages. @@ -617,42 +613,49 @@ def remove_duplicates(boreField, disp=False): Returns ------- - new_boreField : list + new_bore_field : list A boreField without duplicates """ # Find duplicate pairs - duplicate_pairs = find_duplicates(boreField, disp=disp) + duplicate_pairs = find_duplicates(bore_field, disp=disp) # Boreholes not to be included duplicate_bores = [pair[1] for pair in duplicate_pairs] # Initialize new borefield - new_boreField = [b for i, b in enumerate(boreField) if i not in duplicate_bores] + new_bore_field = [b for i, b in enumerate(bore_field) if i not in duplicate_bores] if disp: print(' gt.boreholes.remove_duplicates() '.center(50, '-')) - n_duplicates = len(boreField) - len(new_boreField) + n_duplicates = len(bore_field) - len(new_bore_field) print(f'The number of duplicates removed: {n_duplicates}') - return new_boreField + return new_bore_field -def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): +def _orientation(x: float, x0: float, y: float, y0: float, r_b: float) -> Optional[float]: + dx = x - x0 + dy = y - y0 + return np.arctan2(dy, dx) if np.sqrt(dx * dx + dy * dy) > r_b else None + + +def rectangle_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., + origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: """ Build a list of boreholes in a rectangular bore field configuration. Parameters ---------- - N_1 : int + n_1 : int Number of borehole in the x direction. - N_2 : int + n_2 : int Number of borehole in the y direction. - B_1 : float + b_1 : float Distance (in meters) between adjacent boreholes in the x direction. - B_2 : float + b_2 : float Distance (in meters) between adjacent boreholes in the y direction. - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -676,7 +679,7 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): Examples -------- - >>> boreField = gt.boreholes.rectangle_field(N_1=3, N_2=2, B_1=5., B_2=5., + >>> boreField = gt.boreholes.rectangle_field(n_1=3, n_2=2, b_1=5., b_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -687,49 +690,30 @@ def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): 0 1 2 """ - borefield = [] - if origin is None: - # When no origin is supplied, compute the origin to be at the center of - # the rectangle - x0 = (N_1 - 1) / 2 * B_1 - y0 = (N_2 - 1) / 2 * B_2 - else: - x0, y0 = origin + x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) - for j in range(N_2): - for i in range(N_1): - x = i * B_1 - y = j * B_2 - # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: - orientation = np.arctan2(y - y0, x - x0) - borefield.append( - Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) - else: - borefield.append(Borehole(H, D, r_b, x, y)) - - return borefield + return [Borehole(h, d, r_b, i * b_1, j * b_2, tilt=tilt, orientation=_orientation(i * b_1, x0, j * b_2, y0, r_b)) for j in range(n_2) for i in range(n_1)] -def L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): +def l_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., + origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: """ Build a list of boreholes in a L-shaped bore field configuration. Parameters ---------- - N_1 : int + n_1 : int Number of borehole in the x direction. - N_2 : int + n_2 : int Number of borehole in the y direction. - B_1 : float + b_1 : float Distance (in meters) between adjacent boreholes in the x direction. - B_2 : float + b_2 : float Distance (in meters) between adjacent boreholes in the y direction. - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -753,7 +737,7 @@ def L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): Examples -------- - >>> boreField = gt.boreholes.L_shaped_field(N_1=3, N_2=2, B_1=5., B_2=5., + >>> boreField = gt.boreholes.l_shaped_field(n_1=3, n_2=2, b_1=5., b_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -764,59 +748,34 @@ def L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): 0 1 2 """ - borefield = [] - if origin is None: - # When no origin is supplied, compute the origin to be at the center of - # the rectangle - x0 = (N_1 - 1) / 2 * B_1 - y0 = (N_2 - 1) / 2 * B_2 - else: - x0, y0 = origin - - for i in range(N_1): - x = i * B_1 - y = 0. - # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: - orientation = np.arctan2(y - y0, x - x0) - borefield.append( - Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) - else: - borefield.append(Borehole(H, D, r_b, x, y)) - for j in range(1, N_2): - x = 0. - y = j * B_2 - # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: - orientation = np.arctan2(y - y0, x - x0) - borefield.append( - Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) - else: - borefield.append(Borehole(H, D, r_b, x, y)) + x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) + + borefield: List[Borehole] = [Borehole(h, d, r_b, i * b_1, 0, tilt=tilt, orientation=_orientation(i * b_1, x0, 0, y0, r_b)) for i in range(n_1)] + + borefield += [Borehole(h, d, r_b, 0, j * b_2, tilt=tilt, orientation=_orientation(0, x0, j * b_2, y0, r_b)) for j in range(1, n_2)] return borefield -def U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): +def u_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., + origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: """ Build a list of boreholes in a U-shaped bore field configuration. Parameters ---------- - N_1 : int + n_1 : int Number of borehole in the x direction. - N_2 : int + n_2 : int Number of borehole in the y direction. - B_1 : float + b_1 : float Distance (in meters) between adjacent boreholes in the x direction. - B_2 : float + b_2 : float Distance (in meters) between adjacent boreholes in the y direction. - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -840,7 +799,7 @@ def U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): Examples -------- - >>> boreField = gt.boreholes.U_shaped_field(N_1=3, N_2=2, B_1=5., B_2=5., + >>> boreField = gt.boreholes.u_shaped_field(n_1=3, n_2=2, b_1=5., b_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -851,73 +810,65 @@ def U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): 0 1 2 """ - borefield = [] + borefield: List[Borehole] = [] - if origin is None: - # When no origin is supplied, compute the origin to be at the center of - # the rectangle - x0 = (N_1 - 1) / 2 * B_1 - y0 = (N_2 - 1) / 2 * B_2 - else: - x0, y0 = origin + x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) - if N_1 > 2 and N_2 > 1: - for i in range(N_1): - x = i * B_1 + if n_1 > 2 and n_2 > 1: + for i in range(n_1): + x = i * b_1 y = 0. # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - for j in range(1, N_2): + borefield.append(Borehole(h, d, r_b, x, y)) + for j in range(1, n_2): x = 0. - y = j * B_2 + y = j * b_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - x = (N_1 - 1) * B_1 - y = j * B_2 + borefield.append(Borehole(h, d, r_b, x, y)) + x = (n_1 - 1) * b_1 + y = j * b_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - else: - borefield = rectangle_field( - N_1, N_2, B_1, B_2, H, D, r_b, tilt=tilt, origin=origin) - - return borefield + borefield.append(Borehole(h, d, r_b, x, y)) + return borefield + return rectangle_field(n_1, n_2, b_1, b_2, h, d, r_b, tilt=tilt, origin=origin) -def box_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0, origin=None): +def box_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., + origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: """ Build a list of boreholes in a box-shaped bore field configuration. Parameters ---------- - N_1 : int + n_1 : int Number of borehole in the x direction. - N_2 : int + n_2 : int Number of borehole in the y direction. - B_1 : float + b_1 : float Distance (in meters) between adjacent boreholes in the x direction. - B_2 : float + b_2 : float Distance (in meters) between adjacent boreholes in the y direction. - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -941,7 +892,7 @@ def box_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0, origin=None): Examples -------- - >>> boreField = gt.boreholes.box_shaped_field(N_1=4, N_2=3, B_1=5., B_2=5., + >>> boreField = gt.boreholes.box_shaped_field(n_1=4, n_2=3, b_1=5., b_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=4 and N_2=3, the bore @@ -956,77 +907,68 @@ def box_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0, origin=None): """ borefield = [] - if origin is None: - # When no origin is supplied, compute the origin to be at the center of - # the rectangle - x0 = (N_1 - 1) / 2 * B_1 - y0 = (N_2 - 1) / 2 * B_2 - else: - x0, y0 = origin + x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) - if N_1 > 2 and N_2 > 2: - for i in range(N_1): - x = i * B_1 + if n_1 > 2 and n_2 > 2: + for i in range(n_1): + x = i * b_1 y = 0. # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - x = i * B_1 - y = (N_2 - 1) * B_2 + borefield.append(Borehole(h, d, r_b, x, y)) + x = i * b_1 + y = (n_2 - 1) * b_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - for j in range(1, N_2-1): + borefield.append(Borehole(h, d, r_b, x, y)) + for j in range(1, n_2 - 1): x = 0. - y = j * B_2 + y = j * b_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - x = (N_1 - 1) * B_1 - y = j * B_2 + borefield.append(Borehole(h, d, r_b, x, y)) + x = (n_1 - 1) * b_1 + y = j * b_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + h, d, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(H, D, r_b, x, y)) - else: - borefield = rectangle_field( - N_1, N_2, B_1, B_2, H, D, r_b, tilt=tilt, origin=origin) - - return borefield + borefield.append(Borehole(h, d, r_b, x, y)) + return borefield + return rectangle_field(n_1, n_2, b_1, b_2, h, d, r_b, tilt=tilt, origin=origin) -def circle_field(N, R, H, D, r_b, tilt=0., origin=None): +def circle_field(n: int, r: float, h: float, d: float, r_b: float, tilt: float = 0., origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: """ Build a list of boreholes in a circular field configuration. Parameters ---------- - N : int + n : int Number of boreholes in the bore field. - R : float + r : float Distance (in meters) of the boreholes from the center of the field. - H : float + h : float Borehole length (in meters). - D : float + d : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -1050,8 +992,7 @@ def circle_field(N, R, H, D, r_b, tilt=0., origin=None): Examples -------- - >>> boreField = gt.boreholes.circle_field(N=8, R = 5., H=100., D=2.5, - r_b=0.05) + >>> boreField = gt.boreholes.circle_field(n=8, r = 5., h=100., d=2.5, r_b=0.05) The bore field is constructed counter-clockwise. For N=8, the bore field layout is as follows:: @@ -1065,33 +1006,14 @@ def circle_field(N, R, H, D, r_b, tilt=0., origin=None): 6 """ - borefield = [] - if origin is None: - # When no origin is supplied, compute the origin to be at the center of - # the rectangle - x0 = 0. - y0 = 0. - else: - x0, y0 = origin - - for i in range(N): - x = R * np.cos(2 * pi * i / N) - y = R * np.sin(2 * pi * i / N) - orientation = np.arctan2(y - y0, x - x0) - # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: - orientation = np.arctan2(y - y0, x - x0) - borefield.append( - Borehole( - H, D, r_b, x, y, tilt=tilt, orientation=orientation)) - else: - borefield.append(Borehole(H, D, r_b, x, y)) + x0, y0 = origin if origin is not None else (0, 0) - return borefield + return [Borehole(h, d, r_b, r * np.cos(2 * pi * i / n), r * np.sin(2 * pi * i / n), tilt=tilt, + orientation=_orientation(r * np.cos(2 * pi * i / n), x0, r * np.sin(2 * pi * i / n), y0, r_b)) for i in range(n)] -def field_from_file(filename): +def field_from_file(filename: str) -> List[Borehole]: """ Build a list of boreholes given coordinates and dimensions provided in a text file. @@ -1110,7 +1032,7 @@ def field_from_file(filename): ----- The text file should be formatted as follows:: - # x y H D r_b tilt orientation + # x y h d r_b tilt orientation 0. 0. 100. 2.5 0.075 0. 0. 5. 0. 100. 2.5 0.075 0. 0. 0. 5. 100. 2.5 0.075 0. 0. @@ -1125,8 +1047,8 @@ def field_from_file(filename): for line in data: x = line[0] y = line[1] - H = line[2] - D = line[3] + h = line[2] + d = line[3] r_b = line[4] # Previous versions of pygfunction only required up to line[4]. # Now check to see if tilt and orientation exist. @@ -1137,13 +1059,12 @@ def field_from_file(filename): tilt = 0. orientation = 0. borefield.append( - Borehole(H, D, r_b, x=x, y=y, tilt=tilt, orientation=orientation)) + Borehole(h, d, r_b, x=x, y=y, tilt=tilt, orientation=orientation)) return borefield -def visualize_field( - borefield, viewTop=True, view3D=True, labels=True, showTilt=True): +def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: bool = True, labels: bool = True, show_tilt: bool = True) -> plt.figure: """ Plot the top view and 3D view of borehole positions. @@ -1151,16 +1072,16 @@ def visualize_field( ---------- borefield : list List of boreholes in the bore field. - viewTop : bool, optional + view_top : bool, optional Set to True to plot top view. Default is True - view3D : bool, optional + view_3d : bool, optional Set to True to plot 3D view. Default is True labels : bool, optional Set to True to annotate borehole indices to top view plot. Default is True - showTilt : bool, optional + show_tilt : bool, optional Set to True to show borehole inclination on top view plot. Default is True @@ -1174,19 +1095,19 @@ def visualize_field( # Configure figure and axes fig = _initialize_figure() - if viewTop and view3D: + if view_top and view_3d: ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122, projection='3d') - elif viewTop: + elif view_top: ax1 = fig.add_subplot(111) - elif view3D: + elif view_3d: ax2 = fig.add_subplot(111, projection='3d') - if viewTop: + if view_top: ax1.set_xlabel(r'$x$ [m]') ax1.set_ylabel(r'$y$ [m]') ax1.axis('equal') _format_axes(ax1) - if view3D: + if view_3d: ax2.set_xlabel(r'$x$ [m]') ax2.set_ylabel(r'$y$ [m]') ax2.set_zlabel(r'$z$ [m]') @@ -1196,16 +1117,16 @@ def visualize_field( # ------------------------------------------------------------------------- # Top view # ------------------------------------------------------------------------- - if viewTop: - i = 0 # Initialize borehole index + if view_top: + i = 0 # Initialize borehole index for borehole in borefield: # Extract borehole parameters (x, y) = borehole.position() - H = borehole.H + H = borehole.h tilt = borehole.tilt orientation = borehole.orientation # Add current borehole to the figure - if showTilt: + if show_tilt: ax1.plot( [x, x + H * np.sin(tilt) * np.cos(orientation)], [y, y + H * np.sin(tilt) * np.sin(orientation)], @@ -1219,26 +1140,25 @@ def visualize_field( # ------------------------------------------------------------------------- # 3D view # ------------------------------------------------------------------------- - if view3D: + if view_3d: for borehole in borefield: # Position of head of borehole (x, y) = borehole.position() # Position of bottom of borehole - x_H = x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation) - y_H = y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation) - z_H = borehole.D + borehole.H*np.cos(borehole.tilt) + x_H = x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation) + y_H = y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation) + z_H = borehole.d + borehole.h * np.cos(borehole.tilt) # Add current borehole to the figure ax2.plot(np.atleast_1d(x), np.atleast_1d(y), - np.atleast_1d(borehole.D), + np.atleast_1d(borehole.d), 'ko') ax2.plot(np.array([x, x_H]), np.array([y, y_H]), - np.array([borehole.D, z_H]), + np.array([borehole.d, z_H]), 'k-') - - if viewTop and view3D: + if view_top and view_3d: plt.tight_layout(rect=[0, 0.0, 0.90, 1.0]) else: plt.tight_layout() diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index 73127e4b..7f555c4b 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -287,7 +287,7 @@ def visualize_g_function(self): _format_axes(ax) # Borefield characteristic time - ts = np.mean([b.H for b in self.boreholes])**2/(9.*self.alpha) + ts = np.mean([b.h for b in self.boreholes]) ** 2 / (9. * self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Draw g-function @@ -337,7 +337,7 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): _format_axes(ax2) # Borefield characteristic time - ts = np.mean([b.H for b in self.solver.boreholes])**2/(9.*self.alpha) + ts = np.mean([b.h for b in self.solver.boreholes]) ** 2 / (9. * self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Plot curves for requested boreholes @@ -349,11 +349,11 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], - linestyle='--', - marker='None', - color=color) + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + linestyle='--', + marker='None', + color=color) ax1.plot(borehole.x, borehole.y, linestyle='None', @@ -363,11 +363,11 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], - linestyle='--', - marker='None', - color='k') + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + linestyle='--', + marker='None', + color='k') ax1.plot(borehole.x, borehole.y, linestyle='None', @@ -378,8 +378,7 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): plt.tight_layout() return fig - def visualize_heat_extraction_rate_profiles( - self, time=None, iBoreholes=None, showTilt=True): + def visualize_heat_extraction_rate_profiles(self, time=None, iBoreholes=None, showTilt=True): """ Plot the heat extraction rate profiles at chosen time. @@ -434,8 +433,8 @@ def visualize_heat_extraction_rate_profiles( # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -448,8 +447,8 @@ def visualize_heat_extraction_rate_profiles( # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -501,7 +500,7 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): _format_axes(ax2) # Borefield characteristic time - ts = np.mean([b.H for b in self.solver.boreholes])**2/(9.*self.alpha) + ts = np.mean([b.h for b in self.solver.boreholes]) ** 2 / (9. * self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Plot curves for requested boreholes @@ -513,8 +512,8 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -527,8 +526,8 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -596,8 +595,8 @@ def visualize_temperature_profiles( # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -610,8 +609,8 @@ def visualize_temperature_profiles( # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -689,8 +688,8 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # The heat extraction rate is duplicated to draw from # z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].D, - self.solver.boreholes[i].D + self.solver.boreholes[i].H])) + np.array([self.solver.boreholes[i].d, + self.solver.boreholes[i].d + self.solver.boreholes[i].h])) Q_b.append(np.array(2*[self.solver.Q_b])) else: i0 = self.solver._i0Segments[i] @@ -709,7 +708,7 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # Borehole length ratio at the mid-depth of each segment segment_ratios = self.solver.segment_ratios[i] z.append( - self.solver.boreholes[i].D \ + self.solver.boreholes[i].d \ + self.solver.boreholes[i]._segment_midpoints( self.solver.nBoreSegments[i], segment_ratios=segment_ratios)) @@ -718,8 +717,8 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # If there is only one segment, the heat extraction rate is # duplicated to draw from z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].D, - self.solver.boreholes[i].D + self.solver.boreholes[i].H])) + np.array([self.solver.boreholes[i].d, + self.solver.boreholes[i].d + self.solver.boreholes[i].h])) Q_b.append(np.array(2*[np.asscalar(Q_bi)])) return z, Q_b @@ -787,8 +786,8 @@ def _temperature_profiles(self, time, iBoreholes): # boreholes). The temperature is duplicated to draw from # z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].D, - self.solver.boreholes[i].D + self.solver.boreholes[i].H])) + np.array([self.solver.boreholes[i].d, + self.solver.boreholes[i].d + self.solver.boreholes[i].h])) if time is None: # If time is None, temperatures are extracted at the last # time step. @@ -820,7 +819,7 @@ def _temperature_profiles(self, time, iBoreholes): segment_ratios = self.solver.segment_ratios[i] z.append( - self.solver.boreholes[i].D \ + self.solver.boreholes[i].d \ + self.solver.boreholes[i]._segment_midpoints( self.solver.nBoreSegments[i], segment_ratios=segment_ratios)) @@ -829,8 +828,8 @@ def _temperature_profiles(self, time, iBoreholes): # If there is only one segment, the temperature is # duplicated to draw from z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].D, - self.solver.boreholes[i].D + self.solver.boreholes[i].H])) + np.array([self.solver.boreholes[i].d, + self.solver.boreholes[i].d + self.solver.boreholes[i].h])) T_b.append(np.array(2*[np.asscalar(T_bi)])) return z, T_b @@ -966,8 +965,8 @@ def uniform_heat_extraction(boreholes, time, alpha, use_similarities=True, Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> alpha = 1.0e-6 >>> time = np.array([1.0*10**i for i in range(4, 12)]) >>> gt.gfunction.uniform_heat_extraction([b1, b2], time, alpha) @@ -1070,8 +1069,8 @@ def uniform_temperature(boreholes, time, alpha, nSegments=8, Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> alpha = 1.0e-6 >>> time = np.array([1.0*10**i for i in range(4, 12)]) >>> gt.gfunction.uniform_temperature([b1, b2], time, alpha) @@ -1294,8 +1293,8 @@ def mixed_inlet_temperature( Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> Utube1 = gt.pipes.SingleUTube(pos=[(-0.05, 0), (0, -0.05)], r_in=0.015, r_out=0.02, borehole=b1,k_s=2, k_g=1, R_fp=0.1) @@ -1529,7 +1528,7 @@ def solve(self, time, alpha): # Segment lengths H_b = self.segment_lengths() if self.boundary_condition == 'MIFT': - Hb_individual = np.array([b.H for b in self.boreSegments], dtype=self.dtype) + Hb_individual = np.array([b.h for b in self.boreSegments], dtype=self.dtype) H_tot = np.sum(H_b) if self.disp: print('Building and solving the system of equations ...', end='') @@ -1667,7 +1666,7 @@ def segment_lengths(self): """ # Borehole lengths - H_b = np.array([b.H for b in self.boreSegments], dtype=self.dtype) + H_b = np.array([b.h for b in self.boreSegments], dtype=self.dtype) return H_b def borehole_segments(self): @@ -2054,8 +2053,8 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): # Unpack parameters x = np.array([b.x for b in self.boreSegments]) y = np.array([b.y for b in self.boreSegments]) - H = np.array([b.H for b in self.boreSegments]) - D = np.array([b.D for b in self.boreSegments]) + H = np.array([b.h for b in self.boreSegments]) + D = np.array([b.d for b in self.boreSegments]) r_b = np.array([b.r_b for b in self.boreSegments]) # Distances between boreholes dis = np.maximum( @@ -2603,9 +2602,9 @@ def _compare_boreholes(self, borehole1, borehole2): """ # Compare lengths (H), buried depth (D) and radius (r_b) - if (abs((borehole1.H - borehole2.H)/borehole1.H) < self.tol and + if (abs((borehole1.h - borehole2.h) / borehole1.h) < self.tol and abs((borehole1.r_b - borehole2.r_b)/borehole1.r_b) < self.tol and - abs((borehole1.D - borehole2.D)/(borehole1.D + 1e-30)) < self.tol and + abs((borehole1.d - borehole2.d) / (borehole1.d + 1e-30)) < self.tol and abs(abs(borehole1.tilt) - abs(borehole2.tilt))/(abs(borehole1.tilt) + 1e-30) < self.tol): similarity = True else: @@ -2630,14 +2629,14 @@ def _compare_real_pairs_vertical(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - deltaD1 = pair1[1].D - pair1[0].D - deltaD2 = pair2[1].D - pair2[0].D + deltaD1 = pair1[1].d - pair1[0].d + deltaD2 = pair2[1].d - pair2[0].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of lengths in each pair - equal_H = abs((pair1[0].H - pair1[1].H)/pair1[0].H) < self.tol + equal_H = abs((pair1[0].h - pair1[1].h) / pair1[0].h) < self.tol # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/abs(deltaD1 + 1e-30) < self.tol # Equality of buried depths differences if all boreholes have the same @@ -2667,12 +2666,12 @@ def _compare_image_pairs_vertical(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - sumD1 = pair1[1].D + pair1[0].D - sumD2 = pair2[1].D + pair2[0].D + sumD1 = pair1[1].d + pair1[0].d + sumD2 = pair2[1].d + pair2[0].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of buried depths sums cond_sumD = abs((sumD1 - sumD2)/(sumD1 + 1e-30)) < self.tol if cond_H and cond_sumD: @@ -2729,10 +2728,10 @@ def _compare_real_pairs_inclined(self, pair1, pair2): dy1 = pair1[0].y - pair1[1].y; dy2 = pair2[0].y - pair2[1].y dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) - deltaD1 = pair1[0].D - pair1[1].D; deltaD2 = pair2[0].D - pair2[1].D + deltaD1 = pair1[0].d - pair1[1].d; deltaD2 = pair2[0].d - pair2[1].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/(abs(deltaD1) + 1e-30) < self.tol # Equality of distances @@ -2777,10 +2776,10 @@ def _compare_image_pairs_inclined(self, pair1, pair2): dy1 = pair1[0].y - pair1[1].y; dy2 = pair2[0].y - pair2[1].y dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) - sumD1 = pair1[0].D + pair1[1].D; sumD2 = pair2[0].D + pair2[1].D + sumD1 = pair1[0].d + pair1[1].d; sumD2 = pair2[0].d + pair2[1].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of buried depths sums cond_sumD = abs(sumD1 - sumD2)/(abs(sumD1) + 1e-30) < self.tol # Equality of distances @@ -2832,12 +2831,12 @@ def _compare_realandimage_pairs_inclined(self, pair1, pair2): dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of buried depths cond_D = ( - abs(pair1[0].D - pair2[0].D)/(abs(pair1[0].D) + 1e-30) < self.tol - and abs(pair1[1].D - pair2[1].D)/(abs(pair1[1].D) + 1e-30) < self.tol) + abs(pair1[0].d - pair2[0].d) / (abs(pair1[0].d) + 1e-30) < self.tol + and abs(pair1[1].d - pair2[1].d) / (abs(pair1[1].d) + 1e-30) < self.tol) # Equality of distances cond_dis = abs(dis1 - dis2)/(abs(dis1) + 1e-30) < self.disTol # Equality of tilts @@ -3126,10 +3125,10 @@ def _map_axial_segment_pairs_vertical( # depths else: k_pair[p] = nPairs - H1.append(segment_i.H) - H2.append(segment_j.H) - D1.append(segment_i.D) - D2.append(segment_j.D) + H1.append(segment_i.h) + H2.append(segment_j.h) + D1.append(segment_i.d) + D2.append(segment_j.d) unique_pairs.append((ii, jj)) nPairs += 1 p += 1 @@ -3251,14 +3250,14 @@ def _map_axial_segment_pairs_inclined( rb1.append(segment_i.r_b) x1.append(segment_i.x) y1.append(segment_i.y) - H1.append(segment_i.H) - D1.append(segment_i.D) + H1.append(segment_i.h) + D1.append(segment_i.d) tilt1.append(segment_i.tilt) orientation1.append(segment_i.orientation) x2.append(segment_j.x) y2.append(segment_j.y) - H2.append(segment_j.H) - D2.append(segment_j.D) + H2.append(segment_j.h) + D2.append(segment_j.d) tilt2.append(segment_j.tilt) orientation2.append(segment_j.orientation) unique_pairs.append((ii, jj)) @@ -3760,7 +3759,7 @@ def segment_lengths(self): """ # Borehole lengths - H = np.array([seg.H*seg.nBoreholes + H = np.array([seg.h * seg.nBoreholes for (borehole, nSegments, ratios) in zip( self.boreholes, self.nBoreSegments, @@ -3789,9 +3788,9 @@ def _compare_boreholes(self, borehole1, borehole2): """ # Compare lengths (H), buried depth (D) and radius (r_b) - if (abs((borehole1.H - borehole2.H)/borehole1.H) < self.tol and + if (abs((borehole1.h - borehole2.h) / borehole1.h) < self.tol and abs((borehole1.r_b - borehole2.r_b)/borehole1.r_b) < self.tol and - abs((borehole1.D - borehole2.D)/(borehole1.D + 1e-30)) < self.tol): + abs((borehole1.d - borehole2.d) / (borehole1.d + 1e-30)) < self.tol): similarity = True else: similarity = False @@ -3815,14 +3814,14 @@ def _compare_real_pairs(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - deltaD1 = pair1[1].D - pair1[0].D - deltaD2 = pair2[1].D - pair2[0].D + deltaD1 = pair1[1].d - pair1[0].d + deltaD2 = pair2[1].d - pair2[0].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of lengths in each pair - equal_H = abs((pair1[0].H - pair1[1].H)/pair1[0].H) < self.tol + equal_H = abs((pair1[0].h - pair1[1].h) / pair1[0].h) < self.tol # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/abs(deltaD1 + 1e-30) < self.tol # Equality of buried depths differences if all boreholes have the same @@ -3852,12 +3851,12 @@ def _compare_image_pairs(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - sumD1 = pair1[1].D + pair1[0].D - sumD2 = pair2[1].D + pair2[0].D + sumD1 = pair1[1].d + pair1[0].d + sumD2 = pair2[1].d + pair2[0].d # Equality of lengths between pairs - cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol - and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) + cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol + and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) # Equality of buried depths sums cond_sumD = abs((sumD1 - sumD2)/(sumD1 + 1e-30)) < self.tol if cond_H and cond_sumD: @@ -4120,10 +4119,10 @@ def _map_axial_segment_pairs(self, iBor, jBor, # depths else: k_pair[p] = nPairs - H1.append(segment_i.H) - H2.append(segment_j.H) - D1.append(segment_i.D) - D2.append(segment_j.D) + H1.append(segment_i.h) + H2.append(segment_j.h) + D1.append(segment_i.d) + D2.append(segment_j.d) unique_pairs.append((i, j)) nPairs += 1 p += 1 diff --git a/pygfunction/heat_transfer.py b/pygfunction/heat_transfer.py index 82d51f3d..01042a62 100644 --- a/pygfunction/heat_transfer.py +++ b/pygfunction/heat_transfer.py @@ -165,8 +165,8 @@ def finite_line_source( Examples -------- - >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) >>> h = gt.heat_transfer.finite_line_source(4*168*3600., 1.0e-6, b1, b2) h = 0.0110473635393 >>> h = gt.heat_transfer.finite_line_source( @@ -202,8 +202,8 @@ def finite_line_source( """ if isinstance(borehole1, Borehole) and isinstance(borehole2, Borehole): # Unpack parameters - H1, D1 = borehole1.H, borehole1.D - H2, D2 = borehole2.H, borehole2.D + H1, D1 = borehole1.h, borehole1.d + H2, D2 = borehole2.h, borehole2.d if borehole1.is_vertical() and borehole2.is_vertical(): # Boreholes are vertical dis = borehole1.distance(borehole2) @@ -290,10 +290,10 @@ def finite_line_source( dis = np.maximum( np.sqrt(np.add.outer(x2, -x1)**2 + np.add.outer(y2, -y1)**2), r_b) - D1 = np.array([b.D for b in borehole1]).reshape(1, -1) - H1 = np.array([b.H for b in borehole1]).reshape(1, -1) - D2 = np.array([b.D for b in borehole2]).reshape(-1, 1) - H2 = np.array([b.H for b in borehole2]).reshape(-1, 1) + D1 = np.array([b.d for b in borehole1]).reshape(1, -1) + H1 = np.array([b.h for b in borehole1]).reshape(1, -1) + D2 = np.array([b.d for b in borehole2]).reshape(-1, 1) + H2 = np.array([b.h for b in borehole2]).reshape(-1, 1) if (np.all([b.is_vertical() for b in borehole1]) and np.all([b.is_vertical() for b in borehole2])): diff --git a/pygfunction/networks.py b/pygfunction/networks.py index 002e8c86..9837b158 100644 --- a/pygfunction/networks.py +++ b/pygfunction/networks.py @@ -71,7 +71,7 @@ def __init__(self, boreholes, pipes, bore_connectivity=None, m_flow_network=None, cp_f=None, nSegments=None, segment_ratios=None): self.b = boreholes - self.H_tot = sum([b.H for b in self.b]) + self.H_tot = sum([b.h for b in self.b]) self.nBoreholes = len(boreholes) self.p = pipes if bore_connectivity is None: @@ -1183,7 +1183,7 @@ class _EquivalentNetwork(Network): def __init__(self, equivalentBoreholes, pipes, m_flow_network=None, cp_f=None, nSegments=None, segment_ratios=None): self.b = equivalentBoreholes - self.H_tot = sum([b.H*b.nBoreholes for b in self.b]) + self.H_tot = sum([b.h * b.nBoreholes for b in self.b]) self.nBoreholes = len(equivalentBoreholes) self.wBoreholes = np.array( [[b.nBoreholes for b in equivalentBoreholes]]).T diff --git a/pygfunction/pipes.py b/pygfunction/pipes.py index 68729ebe..a8006724 100644 --- a/pygfunction/pipes.py +++ b/pygfunction/pipes.py @@ -657,7 +657,7 @@ def effective_borehole_thermal_resistance(self, m_flow_borehole, cp_f): a_Q = self.coefficients_borehole_heat_extraction_rate( m_flow_borehole, cp_f, nSegments=1)[0].item() # Borehole length - H = self.b.H + H = self.b.h # Effective borehole thermal resistance R_b = -0.5*H*(1. + a_out)/a_Q return R_b @@ -1093,8 +1093,8 @@ def _continuity_condition_base( m_flow_borehole, cp_f, nSegments, segment_ratios) # Evaluate coefficient matrices from Hellstrom (1991): - a_in = ((self._f1(self.b.H) + self._f2(self.b.H)) - / (self._f3(self.b.H) - self._f2(self.b.H))) + a_in = ((self._f1(self.b.h) + self._f2(self.b.h)) + / (self._f3(self.b.h) - self._f2(self.b.h))) a_in = np.array([[a_in]]) a_out = np.array([[1.0]]) @@ -1106,7 +1106,7 @@ def _continuity_condition_base( dF4 = F4[:-1] - F4[1:] F5 = self._F5(z) dF5 = F5[:-1] - F5[1:] - a_b[0, :] = (dF4 + dF5) / (self._f3(self.b.H) - self._f2(self.b.H)) + a_b[0, :] = (dF4 + dF5) / (self._f3(self.b.h) - self._f2(self.b.h)) return a_in, a_out, a_b @@ -1742,7 +1742,7 @@ def _continuity_condition( Dm1 = self._Dm1 # Matrix exponential at depth (z = H) - H = self.b.H + H = self.b.h E = np.real(V @ np.diag(np.exp(L*H)) @ Vm1) # Coefficient matrix for borehole wall temperatures @@ -2658,7 +2658,7 @@ def borehole_thermal_resistance(pipe, m_flow_borehole, cp_f): a_Q = pipe.coefficients_borehole_heat_extraction_rate( m_flow_borehole, cp_f, nSegments=1)[0].item() # Borehole length - H = pipe.b.H + H = pipe.b.h # Effective borehole thermal resistance R_b = -0.5*H*(1. + a_out)/a_Q diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 0cf6690a..db3f0876 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -356,7 +356,7 @@ def time_geometric(dt, tmax, Nt): return time -def _initialize_figure(): +def _initialize_figure() -> plt.figure: """ Initialize a matplotlib figure object with overwritten default parameters. diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index ebc3c5e9..3665b50e 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -23,8 +23,8 @@ def test_borehole_init(): borehole = gt.boreholes.Borehole( H, D, r_b, x, y, tilt=tilt, orientation=orientation) assert np.all( - [H == borehole.H, - D == borehole.D, + [H == borehole.h, + D == borehole.d, r_b == borehole.r_b, x == borehole.x, y == borehole.y, @@ -87,8 +87,8 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 * N_2, - np.allclose(H, [b.H for b in field]), - np.allclose(D, [b.D for b in field]), + np.allclose(H, [b.h for b in field]), + np.allclose(D, [b.d for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -107,7 +107,7 @@ def test_L_shaped_field(N_1, N_2, B_1, B_2): D = 4. # Borehole buried depth [m] r_b = 0.075 # Borehole radius [m] # Generate the bore field - field = gt.boreholes.L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) + field = gt.boreholes.l_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) @@ -116,8 +116,8 @@ def test_L_shaped_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 + N_2 - 1, - np.allclose(H, [b.H for b in field]), - np.allclose(D, [b.D for b in field]), + np.allclose(H, [b.h for b in field]), + np.allclose(D, [b.d for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -136,7 +136,7 @@ def test_U_shaped_field(N_1, N_2, B_1, B_2): D = 4. # Borehole buried depth [m] r_b = 0.075 # Borehole radius [m] # Generate the bore field - field = gt.boreholes.U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) + field = gt.boreholes.u_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) @@ -145,8 +145,8 @@ def test_U_shaped_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 + 2 * N_2 - 2 if N_1 > 1 else N_2, - np.allclose(H, [b.H for b in field]), - np.allclose(D, [b.D for b in field]), + np.allclose(H, [b.h for b in field]), + np.allclose(D, [b.d for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -182,8 +182,8 @@ def test_box_shaped_field(N_1, N_2, B_1, B_2): nBoreholes_expected = 2 * (N_1 - 1) + 2 * (N_2 - 1) assert np.all( [len(field) == nBoreholes_expected, - np.allclose(H, [b.H for b in field]), - np.allclose(D, [b.D for b in field]), + np.allclose(H, [b.h for b in field]), + np.allclose(D, [b.d for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -211,8 +211,8 @@ def test_circle_field(N, R): B_min = 2 * R * np.sin(np.pi / N) assert np.all( [len(field) == N, - np.allclose(H, [b.H for b in field]), - np.allclose(D, [b.D for b in field]), + np.allclose(H, [b.h for b in field]), + np.allclose(D, [b.d for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), B_min), len(field) == 1 or np.max(dis) <= (2 + 1e-6) * R, diff --git a/tests/gfunction_test.py b/tests/gfunction_test.py index f8297131..4103eb55 100644 --- a/tests/gfunction_test.py +++ b/tests/gfunction_test.py @@ -69,7 +69,7 @@ def test_gfunctions_UBWT(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.H for b in boreholes]) + H_mean = np.mean([b.h for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -140,7 +140,7 @@ def test_gfunctions_UHTR(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.H for b in boreholes]) + H_mean = np.mean([b.h for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -220,7 +220,7 @@ def test_gfunctions_MIFT(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.H for b in network.b]) + H_mean = np.mean([b.h for b in network.b]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -262,7 +262,7 @@ def test_gfunctions_UBWT(two_boreholes_inclined, method, opts, expected, request # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.H for b in boreholes]) + H_mean = np.mean([b.h for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) diff --git a/tests/heat_transfer_test.py b/tests/heat_transfer_test.py index bc0a263a..2dbc73e4 100644 --- a/tests/heat_transfer_test.py +++ b/tests/heat_transfer_test.py @@ -43,7 +43,7 @@ def test_finite_line_source_vertical_to_vertical_single_time_step( b1 = request.getfixturevalue(borehole1)[0] b2 = request.getfixturevalue(borehole2)[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts # Evaluate FLS @@ -85,7 +85,7 @@ def test_finite_line_source_vertical_to_vertical_multiple_time_steps( b1 = request.getfixturevalue(borehole1)[0] b2 = request.getfixturevalue(borehole2)[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] # Times for FLS calculation [s] time = np.array([0.1, 1., 10.]) * ts # Evaluate FLS @@ -162,7 +162,7 @@ def test_finite_line_source_multiple_vertical_boreholes_single_time_step( boreholes = three_boreholes_unequal alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) + ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) # Time for FLS calculation [s] time = ts # Evaluate FLS @@ -248,7 +248,7 @@ def test_finite_line_source_multiple_vertical_boreholes_multiple_time_steps( boreholes = three_boreholes_unequal alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) + ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) # Times for FLS calculation [s] time = np.array([0.1, 1., 10.]) * ts # Evaluate FLS @@ -333,7 +333,7 @@ def test_finite_line_source_Lazzarotto( b2 = gt.boreholes.Borehole( 20., 25., 0.075, 0., 5., tilt=np.pi/15, orientation=4*np.pi/3) alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts * tts # Evaluate FLS @@ -406,7 +406,7 @@ def test_finite_line_source_inclined_to_self( # Extract borehole from fixtures b1 = single_borehole_inclined[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts * tts # Evaluate FLS @@ -517,7 +517,7 @@ def test_finite_line_source_multiple_inclined_to_multiple_inclined( boreholes = two_boreholes_inclined alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) + ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) # Times for FLS calculation [s] time = ts * tts # Evaluate FLS From 7fd5273ffc4b234c8a98c6c82243999c142c437e Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 4 Nov 2022 08:36:58 +0100 Subject: [PATCH 03/11] improve erf_int --- erf_int.xlsx | Bin 0 -> 93089 bytes examples/comparison_gfunction_solvers.py | 46 ++++++++++++++--------- examples/profil.prof | Bin 0 -> 86348 bytes pygfunction/gfunction.py | 18 ++++----- pygfunction/heat_transfer.py | 14 +++---- pygfunction/utilities.py | 29 +++++++++++++- pyproject.toml | 2 +- tests/test_erf_int.py | 46 +++++++++++++++++++++++ 8 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 erf_int.xlsx create mode 100644 examples/profil.prof create mode 100644 tests/test_erf_int.py diff --git a/erf_int.xlsx b/erf_int.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d28f2a24ec446fae0de6363bd44ee7946104b93d GIT binary patch literal 93089 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

}zh z@jr>MeKX-GLN6JRv=N-?Il1))r!E-Ej z(T@0m=Z&H{q7(Q)f|}x=HKO~UH;Tbds&UGR&$D&uBswB+N-!MfuNY#G)2v%ktnr>b z5Fs4&x)o?}R`BS7Gmun-rhxMPyZYHY`o*h2b(&S9*a34a7r*7XA3m9a+}@u zmS-=7=|$(=7Pii9@e-)rbR7J^^Y+lZ(+Rxs+91E!Q!?9;(5PFlL>~lB35Mfbg?#{r zm$Xv53gWDO2h^$5R5=e3^TcL3@7V)5q{GTE4ha078oa%=*oX9>VqqKv{Oy|4H`Q7W zwiYXJ-Q)*`<4izvFFS;(BzTSiT7A^?oBiv@yUx#nNyiEvlBEAc$G{IfZwpOJ3Li*N zQ~aBIXbb0^tFNdR?SFO|ycfI|Ot%W05)8-rmeLOfs3-lx7NwW()|uQ}rgfeppdX|3 z`+?_ep{+~djiIOfp6F+f!)2a*1aet}bh;W#a55NF4;3lI7Y#&O0iyJ!7%8(ed& z;K~2vgnobUye%}obpjt~fVa+?xp~8i_KKRR;PAYk6&1U7e=r>94@iG5Gu;Gvbb)Mz z=zfS@$`!YBJ#4)_bO>=_t zhIZvJC&C%2M+Xdk?jrdok01CBHYKd!AwmDcx%<6HN7CwLKJ3218}6j311)aUAfNCgD)sozJ&i#KVYpfHEJX*1z*d&H|ByB&zDi$6UET% zh32O-6dT)IQRhrP?7VuR1K-(ceXx@Ob~pnE?I&=CPnz3s(OlTi2esL|$a=h{uXZIE z-j@fIa|u>Uq6g;;HV3_S1-pM%@ZucS1^q1y5C+F9@YbFn=SoohE#Q0RebV&m7;yd^ zk3Mmv_~0gD?ESz1&X+wu=#dy?KEph5BusN044E^K%m@fmz??2R?oU+e*@0XYCx1S$ zXZKV%*#K9FaB_$7%Y!hL=*>YIOOY5uf+akz5Rwg#Z>-3$($Jo(0<=u%fQ%!7gak^* zqCX3KTYfv-xu&@YE4-f$E%EAQqHFsd=}r~9O9>VK zjSN3n85#O*N(MSx0=1Gj_3)$atzR>CYXjKn)~~y@%edRU-pdWB_%CGm!^+5TVa^`l z1}s@19BtqqdL0ywcN3H$*$P3{uCteu>)iSZ?i;>GIH2OckU2P-2({~O7G)C9I< zi1H*}F{>Hk7$Ho=xMc{KRCie6d~o)V^VO=k1ul|ol~lNvVEsT4d$0K`)nF~UBC$q3 zxGkt9nc*A=j_J)blmap%fPfOuI(4s~tq^{?{hGdtny$}wz0)UfbihO!(?&q6hy(O3 z9v{7@25&VMYe4ozL#voAkoYE&FhiaJOV^S1+Hs4($Gh*VQ{U|Wu@2~x#;9+MElDuE zrGeN|5kpIfDK_w=-s&}%l{Om<%MP&unnm-uUnogp%nj&>Z zoL9d1Y{~ZrU;zkMf=f180Rj9TQdEJf805#liIE^xLd(f$02JsADH%fHc7Kzu_p0uY zWhniaD|;t>Y9eG1M~zV8Mx`&oBYxq{lAMIpl?|=Mvda8nq3zY4oQg~Px>VXA;0)Z^Lt^6++;uu3!!j`NHCr_C68T7uJ6OojsQLO z^N#~VTPd&+03O~0nbh9k#)q8w8PgrH5M-4Gs~1-o__fR+$0bOpJH)VI5Wf4qffVpA zv@VZjyqd1&)qNdB-28J~v}xN~9-OZDQ8$KnPk30fvZAKsJ7H_IzAtZke|Bg)|NGho z{)i?ie;`bprg!lU6PKKU8zPT(EgY3eQI}QjQm@<>c=_5+c`lrR5{xI$CD$DF??S3$ zV|-V?v%IHL|vxQ=k8TA_8t)}$k2S`6{8g^x3Pa-udUnK&lNO==JM zk?MN3B{{f8Oc?zlq;Nqvb&0L(ir?A{_|u*!KZ7%$Uh9Wb)_Yxk>;9%sGez%3St!Jo zhq#BYJvi^y#XDXh?vhiA<_Lw>#XHBeD?@H>KBE^VBEHsPcTI!$viTO|mkj%t;PZ!>t z1=awY7V}Uv^U}7w_>Q5K;_?Ue#ey5J!^SONraPAnsQnP3>{K_oql+U&a4{&QGO$&4 z21O_1JrOIqL&Pkh9@nFAf%43WEt?mSH0m9IG30t31S^oN#CRP8A{uEB1znt|mlGm~ zBF^`F*&eP_c}kDu&Z5J9$)vKp?P+=;DjLF_CBb;&l-x^Nk#6mCBtzEWd+lE>9CQjY z6X@t62Reo0o6jg_T)Jt{WOClwYQ0JR-Z1I&KbyC|6^%nl~OBS+t zRmVxU8QrRGMfwi@Ks`vQ(C!tsiHuBtJG$K5mmqZXpkkPTjN9KC^_1BKH zhkNw8&3p8CT7{S6TBf8cmWh*68fGxsQlt+g5??17o3vOBGb+$N85T>b>^7KGmaN`$ z3?#2juYlfp8eRiCslch&YTsOj^hz+ziQ*P57I5aaLWN|w(qUHlU0ja|Md#5mSkew}8C5VS zgTjPUv@q)1irspeA>3eobG_M@8Je>eG!i3{=4TkcYU)Yt9mk}O9(VyAgPNQQ{d|=0BOw$ zZ-LjC&=r{D11nqHpbBaR^3H=ZmwXirYK!BcDc43;ys!~m6)RdaE7AAIE?zTe5B9<6 zmwUH5;au1LupRmB!i`8BYc0N}8lSXeLB9tZrYozG7sfA`sAqall zDVrf9DqRAEp|=YaMIn4c0`98V;VRw`*a*VCZg{waCeH^nUm+U&sEccCdHij8xG!@x z%frqWVtYZtSNJo4;3Y$P(~yn}__}RVK0SB-CV__pb>1DtRqMQkiS_L%$K0Ay(f{J1@Z z!n2qbx~}E*p|un>-_&JG29E~=MP3x?LJNs^**(-S>JidS(7LV=5)9kkiOtHyoNods zt4yg9+AXG+7sDl&TlPox60Z(HSkrN5`WinkLramGY{XilCR_F7*Sk7Qhl~>SCTIF1 zW2IW)tfW0htBjgvG%yG*O_(dbcFZeQ)2rETC>O>s?}>9O>ZXbcoOG=0kZIE*bW`JI z!o%-6u!E)UeEa1%aX}QIVNO<-jN2!e)@-RUa#$v1{`yn5h9}S{PEr)tzLmO(5~>1 z;+tA^QS=G4e`}Cq_))nY8q}vxYQG98irhag=rsA*D}Vh{RIi>qCJ~{=2-4#lo)>;| z4$>oZ;%LoegeYsPj@L(p z9agx>&zTD+!%@ngo$fFG^hhKz-#-VLCVOU@SKnyb>76Q*LR5y~hSH&GgZ^4uysn~#_PJBw>kDm&)lyZKRf{H#2H6%3Y+ax zzKVxjBjMz)Qx@l6g%{J?4Jd5-D;#co*npV4m#OaVFCdtzRMeb}_se_l{u_H!|8jEaYExH(zc(@> zZ12AEV}SFi-V!3g07pJ>o+2xx{!30kMS)u+d`1Dz*6ANT&nMU50OChYo4IE|q1v!p zVg(+4;0FfBat-uPdxA}5q%D9bidFU9tbZQBl`BPE@Zzmyd8VX z0_U|K7>*MUFrWuof<^U^5v@4zmVDG#@n@%@HKjsf0!CE2MwJMjWLfgknIH%FG9IsoonR`BdZM$JwK z!q6o3(!p>6%BcYhkj6)Npg8Y}9_ z1M6nhD&56beh$!U=%4ts%69rD+;rNs`wku&r4h>0V?c;b{qS#KK=_qGxE&l`+i zUT4fAuyw-RllKtzi;TT-sz~sj_$68SCVfQlmL~!K<`)^xO)dlLv72UttVl4PI3-zW zG=ICbk^-yO!Lj>hR8t{@1o(yuM-yXVly3>%6TdWeAEzemi=G5`8^g{Atl8ioX9^!8 zO$#-TF*8e|EddVdBC$x4OQL}sfodTb9yNn^B_S=BhxADlVGoAl0JVgVGaqJTr@lrr z7_;!+1YEMjSsq9fKKZEe6{hU3G8wL`u>x`pacF2B=sn>BZHnbC>Z1h9@piD$WCeW) z00W3HYLYvzDmC**P&ll>$Z{oNXF}jya3CC1KpsK}omh;zhXK&`b%Z zNxY%OigoTJA1qNwVRQ?wu}f3#w;|q|7~NuuI`16*b6gpSw$A(Jn+4^54aF@b8Hd^! zTo{1KPhfQ_3p$H@)TEc%tZav(6}5li^rP+f2k?Hn#t@Mqt2ynXfmV91Q$$f?K3;c! zR}Bmvze<^mV?g^O!SL}LkC@@K$;d#%z?W^j^3I8d0kEn1?eB)p*_i>kBpeb9L#|&# z0XrIqADo#d?pPn1`*uSgQbB{0Abxb( z-QVsEY*0gRYd`i0Gc>^jgZCsbg&`z0tAUqyQDEpSp)dGgCM@!lPhMYCG?>|c^@8u9 z_pBgy{?OVn`G0qU+kh2V=UqpT3P`KuNrin-vIU=EHc&FSOPMnB4?!poEBN5Yvg{wt zi@y${#0oMc^hPOcJXnEIqVI+gfLnHMN~kX!C=k;-`>B$n8fSqU!E+%-B~Qk-zD8Ms zVd%AnpinSe9Mdv+uEt`v(6a+Lx?=?oBS+Rr42;9`)`Si-n&9ytZOmD@=w2{2m1#}J zG};S2=rvr;kd3-;@HCEU+kz{xwTDH%YZw2h>)S6$D8VyQBYD5?A#RHn`bV`DUS=%z_!5u|B) zAT$MHE(nrfI8G!;&xr6woHQ#XJQ&{jj}AA-HVY*kAP(DM=w~hK%g@U5QW|}$f&>XA zk|3;WV(%DrMUm3s|BT5C+x5k*I#(Xv40VO%e3xK6aZ0oI&G-X@GZlgO8|RZevme%= zac}+q_qaEomLxSQ(UUS9d`iw>v@Ug#>!;zgdCv!KC+ews#uUg#$yw*uiyQB^GP)6+6I z3S)&Z<0S?^4{k!>IwtB7n5gvy2luSNDgDPMrS$F6zf3ACr0HcSb)q-yCl14Lirxeo zX!t$vj$EVf!3CIkan9`fAL3)#Zi`OPDZwC4q4_15;B$80?7UUJ-S7{{nRE;%!Fb{% zTsP85p9X|G8}~{*p7_-}h^Ph{h{OM*AQ&Ynx|e5OqjCx7@15zC721vmbbZ|a^@Un} zvMG8MIvWHCKP+rI=iIt=>mkqvFU5Q4LpovLZWjxI#g1fyVbQwbs*x2}4czuw5;23f z(G*>Ew_dITT01ye*<~wHs~pC`VMc1eE>;%_hGWMyNyisMJaBfkZ{HU&v9_XK>G*5u zNj5l$0CVaCWW<#gNHg|}WrFVpWWb$ZX>F9KLF&(<9w`eSXC|=8671GN&;zlX?>oK7 z`Y+bP`C`<(rvq!(27wH7*CE?n_dxrB=WX1^h6sG1!RcwPexXXKZ^04LddWxSF7B-i zTt7p{R5&#y7><)X;wa&yud~1*3M=@)$$sK69Ov&y=R{C=;8WQE5=cJkoS;Qh8~g!h z`>cQhFD^^@(=&-fK+pI?#hVr^H4Z`BT~D*;d8YQ0jAnfSVOtKR-2cKo3Qp!}KLJ zh(ej&yZnNMPLlkl8sH(frC9u*nDSID!35Y+a=_xBTheGGoHVXAF%y8F@uEDmyD(s0i!p~DDuiQajT)=V7QJe>aG#zk7p_+*Deb4G3yW6JIx&^9NGpgD8X=?sCo42-gJf> zX@6k(IPh7S=B;Y|c)~cK(;ZS4qM@n61fh3n~UKu;F7%u$;KFFHQTU$@U}j!DYt^xQ2Xy1gbQE-%V2_BMDhF z4=(690|`+epMEI8F>3BPM*qo^OVJU!4If&CHmPHfM7J2tSsslzbyufTv*O{ME#!1D zn0Pc+B?PzxY8qV#FO0+#*jcr5@Ky|u#xpPu&sw1u^l*Lxei5&mZS;{JXSaq8>L&GP>wsJ|@SNbE zgweP;*?Sx8(iOT^00&FDeJ0cBw%;c~>f(I|i#7jp2)F8l%;dcv54EF6$*pni{%wl zSKiUbev`34V=hG+LF!(~1kwC?H&#f~XW+_YoDA$Iw*m1B34X+m(RD<^$wiqJeBfk1 zxs!jK7dTO0vK3d*Nqs;Nn#>A5aI&8`43h{Ay##J%x#E#=F~Df`RmWDt>dk5nL7HDz zjS6qsgUB`M*J4gX0EsptA9ccr^!Y3FhDB&`P=}Ig#_s4GAp$fGu5Y-ffnh8HzWFw~ zZS#L1X0&O)0axn=D=Y+A>`(cFvGmyOjo`w^NC9TZ1Q`~4{mojsqxA=af}C>h(AIsQ zqsHz-M@!(G@u>RZw+F$GO+%2=Da`dL0{v+iF+^t4N9T%jN!2D`v+k)Q;@Ig0HHa?% zwaur=d807eGm=WGaHsB`^?9YGn_x@XF#O?>#V_$e49c9%2IdG7sI39#1xntQlN0_1 z>&3ml5kt=3Kj?Hh@^wPT2tlP7o=?`m2A|=aEe|Tqw^(r zE-oY(FA^DR)X+5F0oV)WTc%&!|8O{PLHB091jBLON6r?958xD>rI{wdRoV5Us9aTU zRR^3J<*XkVt^=`zPn>ik{)fr!VEe-tP6>wN#7(tKCy#7r1s`;>pHx=h zIPunkJ{dkRMxyU^do3xt_%sAQ9Gmq=P~TJ>_mK!=VEMPC7h)V-c>0|GSssC5SA%Xs=7>v*%M?>nw`eXJ(_jxr<`N zr~6!EKfn_}Rf1uh)(#`iP{0oIDBu^q>rv$6GvJ6|1s^!sPaKBh`~`7R3*UgV`ipxD zLVtm4imc!RC;N%RaGXxSiBI&BYs2_rR$-jZ_)(Mg1)W-!2sv(9fg|<<1BhRC_uSC@ z_iv}VMJFoWLe4oeAg#HHE{d|kb`SsVe!$b1$7&&eTLQilj){~DJz~U zDl6dFa0@^mj0_I&2anBk1SsO4(Ie=9h;?Oi@{UHGr)7Y*`{wmtwt5e4gCuJ-{7}7- zxkF%i3US_g+`3#zMIQ}v%~!fcDOLoK7)l4|kBu$I1@_EF3>YWc#^FFl*Z%)L?vBpAf`1z;l5qc5a5 zg{aVcEpV59?c!TQN6l_Z<_yAXe}bLo%ti$*-U>n&12JsI)n2N~n$oOD{v)!08l+3| z&ZOy|BRNK#5)8*l59mda!rjB^bm>rU)L&;9H<(D?A|zf+rluIsf>h z{-EwbD90bVHjT5E*K)VVG^e)Tc?*TKYbe*C9J@4`hj6M^|H87l!K24otLt0 zI(=n)6d3CNEKS^>e~$1ME`~Ym-d`B{CBY+p;#eX10J7oJP+(!R>=VYl8Q*iBJ$5Ci z$(x5TOI#?5#=V&Y!_kj}QDH0*`@N=XTFt$D8FJpxkXQ^JllFTO496)lF&OF8+BYhk zcp8STdiDiRORQ`IA|cT!!El^{ZAP5*;U2i@G$Nqa>e%YQX8D4&1jBKP%WX!S1@mPa z7n2@t^ql?6RV4RGyDyv)496*Mz!`B?t{XM@STTrS&Q)oKL}c(XwF=KjdCV_Xh1cb6VO@PMRgZ!;>biiyog~NA zw4ZX60G_zR^TbM}kIwVtZW-7cWL#X%+PN9T=esk8i3O5+kPi#Ho;QpBe7&!Cvot0Y ze5dIS%&VeRA5PqZq>E$l5GVv6lLk8s^Ryb*Z93Dzo6)wyy75~|4B$>*lCI_;)$4*P zu74k`Ok0=kvBtqyWd))naNf_N$MP<9gtsPpT|yK-$F8G425k>iv-nY8F9|KM|9lfg zU3pq5Jo3#H!8<=NoH3YoM4yPVV#H|)UG>ZBFF~G$4ZFK*VDchgI3*a4^9kaV{Dt(& z0O-(ZKb$zdV8Cn8p;8JW35Mg$0|O%Cq}#*;Y`=KG7fuO=*O4Eu~M6 z8gZ_B7nV8C8908LUT0d*DOK~poM$1exU3^$Rf6F-MH)gQPMRbQ0-I>eC3>hTp}ZNT zUxMK{MevgmC(UW`&ZKVW{_yh|=j-{xDZy}@nb4ea%aY&~KO|uVA9S*x+?#xwX^hOH zAfGrl_ifR=RDbYivw{zt>?aPx=?wOXlfNCr3O;bMpHx=hI758m9z;5>T%95woz2S4w}Dybo5T3BiVaC zOE8=cJJRu=q+Rk!1AlYs{-e*%#gy<#g9O8AxDPbQ^Iy{GJP2C!U4r2_N#gyf?lq6| zikXe-wTKt-MGEim#VZMh<9vs_%1Qj2b}d1JH>tsNfQz2S(ct09eOJt!d-Nh+_c`Qk zEJ!fCg+d|F0=!Ou;hW$fX9JH-h+GjP60_#K^bqcK(i9dLaV`xwizabj?7VE!9h8d6 zAR6>7sE2hjY;FBDT(a*Tyz|7~gj~V~sf$z;=2Vbe174|U1GTj5;6K2RYUy0!z|W;8 zz`eh2b)pLA`czn0!w^wv4@BV3!rS{+vlqeue+W5$A`u~EDRlG4C#FCR&rr!n9gyEt za(Q@DkvK0+zfJhy4~DmfnJcv!KlUu4Ibz<$bGEjPA{A9YCo zKA(T@tfHtF;=h=Gt4@CwHloGBsW|zlpEOz${X?dHa0KiqJ@4I&X@dQDrl%*9W)0Ik zWm}d-j!85t+olc7Vu@A(BBunmc=*R^SEdtkprrv$@s zV*Xm);;k04TyM1I(gs*^$~~(U`0HqVrvAP+KP4EB6OVQEE)=KO%CsI_LjCpN7EbZHv@8Jx*)v9sj{(Nco!E$loXv_Gi5B{hwSY8r0bNDE&hsx)R5R32kq4Fgd z-jzLwQ<{y`raQ>I^v7Z59QKw-35Mg`=MyL2e6TIF51bMV$0;_l#@R?8T!p(VIRX;$ zDK#(;k+kWNU^vcqKIvqah1|!-cxy;X%qPKcoQDvngNlZjMiVP;9JV5NeMLR8ro3(D zp`U?H$yk$MIL?t7pENf6E}QP0Fwm6aZWPODfmeZ%GKig+`lN?;jH)s!`zhc2H7nw>egmLd(r zNo72nkl;Cf0dK_5Q|%5IezEVyQ@C=w_d$?{k- z@F4*1m1A8PodJfIJ^8sB&Cf=2#1ix4H=vNbYeH>SVJ(8ylmNexk2?NYK#`!*Q{k=c zqlI>UwGON%MnVxJp%Ay}u)e2}2xo?V@bCe8gLH&)JO?}6csAhCL8gg17sNSA$2t84Il%J(aj?rlGt4LAcS&N|pGepH%y z`?5)msd>73^%anZmqtDsOu`>RzPPb3}z+|(4+&N(-%$&hU3JP{kqkAV$&CM;mRM~Y2d+_q|hZ8juT&~ z(7St^SJ$4t8L}q@%&Th)zrWumX`aAY6~|zwH%u%ip{h=7yGwTC=|lunttuN zST8?0cNBM3%1C!r%u$*hVpXa@@^&Gc5nm$_K_tuvYJnM885l`E z?mf;$w%NHzE>n}<24in?L!d{DRfpG#lI;-Y!+P$gK5uj_HX4kL>KF&&(JEiKSs_g? z3E zb_ld{S*(R0cYbQPAV(8bmxAQzlo&C4fd1LKJ^4B?GK6?<*e+;#oi!dn}gpH~j6jQLe#=|nd$%^QZ}#NpIS zQJg~AM47TLZL$~kr3>C(EL7thW6Ms*d7VHj#MqLf;F7&jkMhk9EBJ_uVL!P&;s=GCj{%RkGY1dsVJ{6BpVs~P zhf1>w-75??eSTj5cclXOtYJayr_qeMStIwx$}8be(D|Zo$?`|S@RX0tqr&JJ%+4Z! z71IS6%%EJ2ns6+qac^AgfVjRdhdsps-QAifzNK_lwON=+z&h- zyiC~hO8)6cKq?$@@MfhgF&c+eAa1?t>b2N*aE&y5gIQ%i-dPQ}-oU$Y35GY0Y}YX% zIN%hLMjiQ(jDPm&$dB3vV4eQu2(vfbIc3#CI^ z9wiu#vk~k`!OEt~2E2j|Lu8AGB!BU6Dbk}EntTqFUFhS1rGxiFa!b7~WrvvX5)5yq z9X3;l3yGt}1K013Xc>8v4zSV2C*7eoJ0|$q`&7K+7 z2nM5vQ!Wtoj3ce;$$f^O%2pH72v51OVfv~%WM+_emQ2tJFq?)yio8k5NuXJP0kBND z@Af$c6v@2zFUYc;>(H&N)t@cZ@&-cOOSTQ!t z-l$t3VF7T=qsdZArQ3&1uy3Pxk1_3DYuYn`UxMNI@hLBM_fNo|H>I|k`VV;gp4RJb zKl<=62pQV(%VpJ5X6rtlqrKFpf-eSHaFk7 zX2MmoJtf``3}?t-#5n-rjZZSZQYQZM^rJa&Z)I5K`-gXaA;xtVfI5RA!j8Vk1H&p_ zT$F0@RCgr6vqG9a7I|k}YS>R4hU3HxS~AYNzqF~bYYFg# z6@1`iKXDk2vzSku^pZD3bh3gEoa`qK!*Sw8P?=7yG+Dt1PWF>~lly|$wuGO?3*LgC zU0;vuxwt3n#8|-xPWBUr;dGLSWcPMN;N(vxu!0Yq>?il(bZIhs37sTpSi?f5KI8G7(EzwEO>>(8vEBL_4e&R43CkFJ% zbPmpPVRFl7O_%|KN12k$OE4TKS=}W%4{X~z`NcE1SzvUh(dEm&hMTLbKK2@1PR=z_6}ppQ2i- zf$)I__(?TF>ec_UjiLtQlknXUrxg6Q_uI*NoL69q&uPDX|ISMnd{{#CVN2MToo>Ep zWZvbweViiF$n-ZRHDzkWD$|Z`^9r}(1kPup z&knj@8a4{QcY84{`8eW~+_w@8$BE7tMyJ@L?XA^)O~nmx{5$bPpHt@x9|N3q=-B#! z;W)`v+PaiZ0Z(5AgY&p~_2x}{q5w`P?1+BR$0c5y)bO_pnhfi| zlgI30Oq71f8=QvJz-Zmm=bVTwwo{026n60uGz1c*}!#XBxw- z0dg>@CN;yTfv7#f6B7KW1J@S4GkU~9Mg3>PiD5qsx~}Ls2?<)pz5s$BHCM4|G0mIZ zQq*tXc8>TWS56@fxCz!jqZ3!m4yX3r={5u5K%xBjA=7KNj{`1CGf0BrTo#Y78h6+9 z!U*hybH6S#zxah*fK$5VBEfK+#|^A9LHM7BANA;+z?$g`{s^pc7t-{>00^Tc%)&mu zJZyP;^jNs=>nxUFx;LTzVT0I2WY3$0w3ay*fm;~_x>-OHC zR{U{WUqUFsaGZtE73LmwaTWcvYe?O^p>P$w`rK((^2B0(eoBhOLI8J=Z5NS+dPMMoWO(7~ z@`LPiE~Wsz?hE05U^wob(0#T~5}7E^-t7F?RuRsmS%Kj+WVB>IaTtyhMw0E5v@_X( zPW-5|vsYU+;d>4%71Ouk?A)35=2W#p8Fp&3#O9chE2(Ck};;D|QFwSNQpuUo%DDC6kAw zGsegE;3USL92KLbV3KakJgtK}0GKE)&BrYXdt#d~NKyMalX?~`p972pHxvE9FeZ|e z$$BoLYYf5}^6@hLr@Pm|(OLDkO(h~J6A4XuQK)lxrdX~~9gbBV0C&hoJ(B0d_M<;t zftz(-ZJa!7Y$$S+)a~t~ZuQzwHye%ye$?vtdS1RQa5g=>R+AwI?+9jK!}=6x1UfI; z>b>+Q)6|S6|zxpIZ8KAX7 zWriPhn`6-4E(tImU6&1Rkfmj2vTSks^{JEuLGrEf1@?S4vm*#x_|oNlkKf@!Y#3wy zB#im)y8&fHCs9dXt~2fl_3DS72KB;tJVM}lD;a2>-AWYVoM$SA#?bkFgZ&@mFov~*@}YO!0J*+8MDdX5i0Yk2_AMf1zL((p?# z47ckN>7HA*JjR`h48%yXf)BQ_pEwM|tJed(Y`qsOqwrf8&MhzpD+8d%Jdj`*PS+iz zHCwnf){-3W@Wl!?5Mo4RQI%jAdaW%=Q_(9VG>|u$u_5ke?pCuTM99^|; zUc5ZmLscahT*K+n%5O5C73BpAeb1IG?F zX41TSSsT35FqmkC2T2M!VG^ydazyEL(b`cPor7kc32nL7aH-;li2i4AQMC zoU{_{D2|@#qlrgM?ne`k0C%fVPWXXgh;`g9IJ32|=>tJ1BH*Q$crWEky$f*Vj1_zc zAp42KaP(-2JA=cMlUhM6q*o{eT?%%v*xMbx#6p5$m|bTP^GD`p9)e7RNY=58UF&?I zlwdeYLTvUXQSk7MCEC&1$4W%WIm$>r>I=UF!|=aHFs|lbarp6YNa4}rzE&yYmM~v} zA;BO{ZvN=KVGup|z#gx0=YS{#IHl|>5{w7VmvyeX~LukPyzM$F3@>F?#M6N+2DVM@OZFa|gqM~0;8=7u!IJtflHn?ebo_#wnn(5$uMXwR zq3{Bn_M;RB5RF7WD!}6#meHbM9F9ho3kP$yBjfe-& zE-P>XVVK;J7c}x34(3=vzQV=hY+1pRKm!Bq_*(40(C0#@hVDfA*mEAd;A3hnynC~D z$Wm|&vV#07A3Xs8XEQ748kJh%=2IZWXO7Q7tXRRL3p!>;g;^oskE|@wKe~NI$nx7R zq|utugFy@>oDz%&&X*PVxZ#xl7pCYrQQzRleOLtVfYMW=tYn)w7H1fyrq=IJ2@__r zw1~3^D-xiCA9dp5+qb(l%>o{NcfpfVF@Vv@3GjyaztOrEX~mDa^oAcG*v$d^VFGoMek|j7&QAo(v=;A4L~xq46Si-^*3ZRRrR&-38Ci3mpLoW385d zo2hz1qys7dfjxyK4hzzM zvU6yj0-!}%!LvI;l60#*&hoE|mNpY4o_y59`tq1L`&%l!kf!6?VjgY@M!Gw%>H*BU zn-@7kyq(=hmtD88KhJO-?Dcg2-T93wfXa6l#}(Li$+ht)Mo`< zp@S^a*DbEG>yTAeqg>vAxPFaB+FxN+N@xxzx;04*Hk;!#G{wc*msBOlc+~-DEgMCiy5NN=;IfLn);j$@gD-J!|jhdH3rzee?Ux zwtl_qJo~ox+H0@t;z_G6iF*R5#l(cdepCZ?Kc0VETZpCth>dI_4V@Zq!|NSp)jkLt zk^rv3bltiij@PS!5J3T=l+)gDfdr~?=ZqqWEHC#Y%s~B<=0uIR{n|6ewt2ieya)oo zFrqX#(IHsL;=d3FEBevosis^|v#u3%|9j3HlW0w^w2yh2?XHN3=Lz7JH0rnJ`t=V_ zybw0C0EmoEQ3iL&Qq~v1q{y7#{FI5)&i)P$PA9*kgA@7Y5&M8jj6+J_@p*}pEx2{t zFrKmkm<3OR6YbtEcnNB-pR8*u%Kg0T*H7-N2ji6hrt35~(XQJVa>IkZFi~8TIBEYj zx%pW?^!<3wfaY;efHLK2-V?eSUYhdFvf`)Uq$GfFGb^U>m5Ga60Ckwj#(D&p^6>&h zXVKVEZ}ZDrzq?U@$nS^`>c%^##lAL&@5P!4K<`oFo&K31EJZdi9Qr5lk8V7UfyEpJ z5XNZl0;59{0AHl?SjrZOs~Rd2=h%=1y&K8j<8hoY0Nm@9bmOgTxPQ&Td+@XnK&rPW zLZQa^2^v63hS)>$+UzG=&3zf~WC7fCy2t0gIJ{dwsAvT^kwAGs1j++3wVJO0yP5ob zcvJ}>PFGeATHJl4x1|eI0s^QK-fyz`D{2gPFn}seMlogt0VJV@y+~M%^|7eBK90F> zPg6=o04e2Yzrq^Fnn+)v)sv{x=(t2V_p#hqb_3YeA2nM9P?dQ`7KLT;?IMeBk4eE4 zI~F4c3*bhW@^T;~MgS3m?H-9Dz-R=-A)M|ozR30;+CF4VYp2y+rr@W?2+3ION=gIpvL)5%8V(OV>6yCQv zVqlvSa}}&?09!0(%f&G_5RbnSN>(HaJG)`7G)Vj&lxWWue`UFhWqHgEwsME-#OoY? z@aHdEw1;>nfE%45<>*aadRLA#*@^{F**shrTt5RqeXB?1x5Th?_+#S|x*|NhPTU^VF@s_W7};pRMkWm$v}wVji?b`P0@-%U_%i6anOl zvyU<%L{9*@c`QnN$;LU`cYTU#UIE}K&0(-(VhEj-)~ne+s39|=q&AtwX3 zu&4a7{R)RYRgi^Rok<(ZlgDxk;D+cOpBA5R`nl(VjRb)B*jx6>wboEqM*w+ATNHlN zgoOa0w`uCa zj!jMoy&MKRU6z|dmTfr<7KJVGcw}l9dOCu+jn=hc!_qGFsH@ zan5x32?hYYnVu&;e~8ZH3(S<#;77)!vS|(({;Ln+1Ir)79>!;0J*)qf@OAG$@bX6s zUOfQCL1jHppQ1i><9+l~;lRl&72aV1psF*Hz@<;G`3%)I13=a0)vASm z6g6I*?>ByQ-yqoP1W>gms(H+37|G7qtrS2?A!()LWKLW`rHq;XQ;S( zk5TO5G(^OC1P;g42O7n1hktbAHJ-k^!~IX8WwGmr?9k49c;BL9^dK_-jn;#YlI>o$XWjTeA|klSS*`7`tr8QMJ@h8Oti*Mw3`W_2k11-rH_IQs z>cO%W5NF;g7~l4s5ph+6QBquKlDSRQ)+j#Nd3ED;INtMy#kci<3HFf|l}C?8P1rj8 z6>EQ*^5~hZR<3~7>&=G_{?xr|eq5c+DE8IHVgn2!Rn~p5>VX;9#s=IGQJqhXH@xzp ztaSsibYt_fA6Z}T`nYP!U$Gm&B|70KP^tKVrj`5lh^ry^OUQ7vo4WCu*YJk@=f*hv zBx?PkT6bimR1Y$W?Wp4ZWeE7gQY@R}&b;9^PBNm|A*obKD;Fsh(*4X>oODGRndhcb46E z7Iyu!Uk0@7RepDL=nkWL{HyY>A8v!(t%K6Bg_L(*Hf+;t~)xWqS`<$M= zp~_XS-UXrH{twSf3~AmmuHH&UVP1lbY2A8D-_Eby5Le$birtKb4c@3W_7~(~8gRv+ z&3AV%hs!*pSRs~O;p)V_?$`C2cEidsadk|ige;r0XB_YHWRFr9KwjH9<5=F3xT=qx ziU`!EvJVTXs14YNQ7qpv@)Q@}{V}F4U=*u5GcmCF)H(Rik<-En zuc}Grt7=4Y&9R{DKhud<-=1Q&QtpXGQn_0b8_DCP~sqsFGZED_o_4znq0g$3ccI1SDPXH{*qxfSr zpPB_|z_siE|FHMZYGO-)#N9?2auH_SFM35J1pd z(ZpIOqc83}{cpXO!jd6?pljEOOh&g-rM#@n6HCzd68*N*=pk388KYWHoVy)r9{~hi zo9sIB*R3PgcV7OctEdh1^Z!z_5e>Mcy$b}*{9$xxP2HhokI2c) zLIXDLBWGVY$b`dl_s4=VPWxf;y9AUL>CnN6bf~t|0K;WsSj`0ehB&PdmFfv?rU`A~ z30aZG!H<4_2Y+8L0ExC9*KkDCro-OC@w}qC_dmwUq#hE5zUgw%*`89O7=m+ZgnC@&gRJB zNAM&Pzzo191eBi)PILh6%m9juiYZm&ZyN#28@O5pNHGHW$>2ms;9iDVqw=zg$7E7l zT$&g~*DqxZ(~URco?BuMJ{W_2BLGbFU>clgk0~fW)MHUGb!bBoFRL|i!@2}KE(I_h zros6Q9r`0lV-s@s<#XE<8k<-4t}hzf6lCQDnaWQFC)#0(H7zR6Q`sd&3TZi6L{q(R zUFpX28hy~e#yBKX5CB{j3=oIgTBq26^^3T){t{DX@cFllSiC+0rPWJGDw+lk!znDu zOdt}9IL$0Nk<@snzIEv5`ak9&U^M{f)W+!N`>44e4F;A5Ke`m>rs@@fR?fIw>esSM zzSrK7_?~yb;dXOhX>g)_HA>YNWueRU5$qN2Yrd1PsRele6>h1msKxn+khDoLZ8gttg?uMSSM*XF6Fh-xDxqp}jk`F;r%JEf^= zuBy54yGQ@MWB$#s!U{l&WxbyZ(6?I114x4(9YD%8E`=jSZV{`{=%bpQv!7!Pad@nu z-QYjP&;BeBx8!WHtK;BDJ5EVRBNH@rnlJ@vvM|M(O6cSTKnY-;q*YIdq1alCNn;DT z#+eFSIvsk%vVB0mK0wV*Mz^D9E*rM|eVB~G;|T?sMJH$-=(3Pu&qp3$z6+3B5?RA@ zGxLjbZjW3<)_YGXP}@?}7%bRJC3OZhXYxqw5}B8*0D|6-(9caqKM74<gDg_=BuH2BfQYj(mh;6!F27QRp)3%g{jm4omj0MgDe zL$%9fYJ&@)1}CF9W|)%=s|2CAnYar^B1vW`h3C_W4W=e2KM@Lw5_5}1aZS}h-586)7R2YoCjMMZEHW0w ziL7!xMVyYJt7tv8=pY2A!Y|6?sOh}i>+oOoo#@S|g~&zYoCit%Li8$AU}WrDY}!N3{JGWi;QpzU5t7D7AulvN+|v!)q1n$7j9Pq(81Pm^jZh@ z3>&*G<8<~O=yXfy2v|i~dcc;=zk zh8Q3K(S~M1EocN6CesF=re$)h;4<7a8O6!ctlrU62nm?wuk@OJ=z9p50;tXC6SJ&G zol+ANoyF6J%g| z)YEBEw6=Q&pvaQ*y@fz~0n|)Jv76}LesdVbvH{-bSH$n-m6#gHD3)aTDH7#Q8mXy!_n4Fm@)E&dzS!509Pn>Sa4)i>Ooqzu*GEuXE)!3-6;wIv7 za3bG4Vv~j7iD;h9Bx#44f}aDyLpNUAbID-~3NZo7)C9e{6_*gr7k~x=*LT?eStx<- z-0{ZQQDefuH}adsJpmL2a6z-W5E7+sJa$M$AcFwn7)tSl9Aqd$k9D?p=-DAHDS~$i zbwo{|=*5|n7=H1zv@V@Y@^1>szZnZG@BU3Vk-7<7Y!N=dzP5GaQd)%^E7-e&Pes^| z>r_4o*5AdELZteN?WpFqYL~Bi44(V~xOE2DA__MS0l-Cc#%#*Gt$K2&SnojH*tpi_ zIz#sDh=t;6RynbJbe z*J6D31W4haEk7BYNS|soQD%nPOEaa$%WC&QX0s&--QwcTRdHe#5l;AC;u5jDB*JKa zObyedss2Q(K}}F33uzVE_otu0FP52rAPBfYF#(6^;C4aH3;qV#bi2 z8%TJNIU?L-0v#G^O!&G1%HN_lahUj)D7@$8A2Ug8xR%nGmktD51=?H@-(PmmV37hKl}0^;h6xk%4!4UFm}#BbFw8`qDndPx=7hy)NdkUxhGH~R=_e~S-l38we%W{itl|R5dR0uKP9LB(9q`qS zpOjt&FFpa>_2QX^@=U3x@aLc^{C%M7b=E6N*!Jk5;t>2-Q*LxfiD&!<1wh7?dYgL9P?Td(H{R@hL-wgo@G@^Q zb@HSdXLp4pdnu31zBf1r3)B-!3$v%Ju8JyWyORfhQ@ukyc1?jtOo!2{u8xhpC@#6B z?2?LuVGtB@Zq&_5b>xxhnb02vDdX_^>-p*02c91_Ugzk*xKXW$nofitY#IHLiyz%G zS5szEKW2|0aoV4Epfa>TXuNKZmzNBE91$uS>e$VF&R=YP{U%G9KIY-b-+A34q96DXOgpDzz?h zM%v+$zCXbf*n)7viE-}IN_648y9yZ*+mkIrff4aC#u9h)(`HzoZNS;bh3L8Af$`at%p)R!y^iP%K}R0gk-RR&A03Y)YdP;c4G-yO^S z%mu6u2g@k9DFgr)XiY3dKMMlk^)UShYCNvIj3)($0DY5m`Mc43=ID6&MI*BFwT8nE zkV!sdDosIQO!HxjYaTC!zEt!j!Av3)YZoDyBWXr8Dm%d`Vl(_6v_I5%jgMThXKC-Q z@Oln_GKKe&p;1l8Oe}9n(#*GFt{Ryw7cfu@5J(69zlmE><8kc_q%RRbz0MuoLMu*< zv2Ggx8$^+b>d(|rDogO0Yw(Y5yubH*rG;7ppM3#bU&zHRzrE|p+EAGY5M3=d=-BKb zR!bYk~e?Vo{_RQq@~Y49U`tF43&)_NK~K#e#5kN-NFQLjG=ZuhG) z{qjlI;AnTz-wZ9{l;;=Um~|_zNda8_ic3R%cx1GfT0k_pYz^RA?Zn7xt*yasS@Shh z;sz1-HpKp4S{g?kghkb^+=HuOxJvJN=&@w7QgMXtX^7Kcvaqocg@ZaanY0P6+yW>k z8r5Zg>$#&1_=h7;W;~Vs#jx&Rq5@!C+96!~IQ8ax)@DFoc6Z4?{AVDv3*geCSmlj* z-p$|R>>WDoM`yJ`1B~lxK)5{m6SYxevLzZINGegGoe`hKh6jqFDk$MIpj)HDEmcS% zIDx)+HhxtT`HMCeh(xoSuy)rj=I}9fKYzvYDED@sg5EH6%MBei=E?|+5?1jw=S{L< z9a0J^xEFqiVRrjL=6-JsNs)s%I<+rt9#`mrDCks0SzdqErpN;bzh~}ZA;mr{++*U% zG5nQ~JArh1h!j)7lhbx84AgScqGZX4pLU^ed->u8weiKVjBc0O*)X7Mop3QONYz;1 zlA%)Cj*l7qLPD zL|FR~*N@IzD}Dl{EOPR*6Z*mSc?ZxcHQt)#Rch>gA1;all(`a_WTP@^sjxGBFnV7H zeO;yJ;lEA)ff`ew*8r-oMp6Cok8Zrl3&!m3-WSQh1Q4!eRUo`zAyqCqT?P{K+t63m zGO^Voa-hbOj3x$f9Rap%7*VbBaVo_X0QjPI1lUYp`B;C^zo$OBpvJqiWd8tT}%0GCYpc@J4Zfas>CkriMYPjbd|7&$V7 z^5R=A>EJ~BOY?fk&nu)?SsrLsH(q&@X1!}HK!~vbsQ1Pk*ILY+?Af9WwMXo_&*l{6 zUjl`N0IufAM8pc964*6)(xA^ufhTC#m+QZ`-dI_X>28Hc7b}qUk{9qx?7pJLlw3A| zu?JIcDe`!Hhk1pwXaka)86tEzliSvKSdMlJ>M8cmcnnrPDfS-k@jSxDzAITh@Y zef+y8o<09{xM2$*$=vKc>(oia&~_RUcHHi9N@o_GL`1fEQ79~>!gHYdn`QF z1aMP}I)@i4eS~&X+hOn4YZR(e#z|VC)e=DTq*n2DZ05pt5B(MK#R7;vdml+z)OfL+ z6}RUugM1`FR2Xrie~d&+?(nSZvRli-K`472^0Q$E*xNKXksj5(grDKpsf7_zXzd7zrYPz=M_Rns%#@`vVI5YYCoe8GKT2tm!pa17BU!NIwZ)A2*!81x}IX%4}(YujR`-@Idvc@$Ri#rTL@X z2geN{mWGgjuQ=gZZ#?JWy7%|9mUZ~czhj;NuG>5_g%u*wKgZi+97rZ|q;M&epA1g4$29MgAob7#)Ezb68QYI{+Bgp?IRQv@sueZ= z0HwBv}uz9uF5NjBRxp=J36 z#hKYTV@iEx!-S?1)1W5s=faDc9lI#*IS11Mb3_|xX!$Y=d*z>o-+)qwP^bwmkoV*! zd5?5opn05FY_qD%=vE5gEb%+VnR927HRA-cP&p_PIeaZNH(wK6tm#N{G1$S zMAA63;E8UP>wBx2#oaWE#>um2Q!@GWs~&)zL4YVtx~D+z1+s_)yp2wQy@L4p0C!&j zjHk|i%{{pHH2BfpQv?AMC7^eBy-w6`_RtnPeh#Md&NTJ@+_@zQ2S3_*iikDpJZq#s z9+5Qo2TFq=deeSWN(Y!40%fXCa=#8vv|m>(Qjo0@qq6f6+?SoJZD6*)@|KTZQTLHv zV6y>`qCfe`;GC?##Dqj9B4fC=F6l+Ap6(}9tv~Y$Bz_Wr)>z9@+N<~;mZj3*NBg#+ v^|^k6Y~$=Tla-%90MW=y6j-BWjylI$>T%c(4dy!9YpwVSXgv*n+qVA)s|bQ$ literal 0 HcmV?d00001 diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index 73127e4b..473b3a17 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -866,7 +866,7 @@ def _format_inputs(self, boreholes_or_network): "\nSolver 'equivalent' is only valid for " "parallel-connected boreholes. Calculations will use the " "'similarities' solver instead.") - self.method = 'similarities' + self.method = Method.similarities elif not ( type(self.network.m_flow_network) is float or ( type(self.network.m_flow_network) is np.ndarray and \ @@ -876,7 +876,7 @@ def _format_inputs(self, boreholes_or_network): "\nSolver 'equivalent' is only valid for equal mass flow " "rates into the boreholes. Calculations will use the " "'similarities' solver instead.") - self.method = 'similarities' + self.method = Method.similarities elif not np.all( [np.allclose(self.network.p[0]._Rd, pipe._Rd) for pipe in self.network.p]): @@ -884,7 +884,7 @@ def _format_inputs(self, boreholes_or_network): "\nSolver 'equivalent' is only valid for boreholes with " "the same piping configuration. Calculations will use " "the 'similarities' solver instead.") - self.method = 'similarities' + self.method = Method.similarities return def _check_inputs(self): @@ -1104,9 +1104,9 @@ def uniform_temperature(boreholes, time, alpha, nSegments=8, 'disp':disp} # Select the correct solver: if use_similarities: - method='similarities' + method=Method.similarities else: - method='detailed' + method=Method.detailed # Evaluate g-function gFunc = gFunction( boreholes, alpha, time=time, method=method, @@ -1213,9 +1213,9 @@ def equal_inlet_temperature( 'disp':disp} # Select the correct solver: if use_similarities: - method='similarities' + method=Method.similarities else: - method='detailed' + method=Method.detailed # Evaluate g-function gFunc = gFunction( network, alpha, time=time, method=method, @@ -1339,9 +1339,9 @@ def mixed_inlet_temperature( 'disp':disp} # Select the correct solver: if use_similarities: - method='similarities' + method=Method.similarities else: - method='detailed' + method=Method.detailed # Evaluate g-function gFunc = gFunction( network, alpha, time=time, method=method, diff --git a/pygfunction/heat_transfer.py b/pygfunction/heat_transfer.py index 82d51f3d..519cbd91 100644 --- a/pygfunction/heat_transfer.py +++ b/pygfunction/heat_transfer.py @@ -4,7 +4,7 @@ from scipy.special import erfc, erf, roots_legendre from .boreholes import Borehole -from .utilities import erfint, exp1, _erf_coeffs +from .utilities import erf_int, exp1, _erf_coeffs def finite_line_source( @@ -1119,7 +1119,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erfint(q*s)) + f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) elif reaSource: # Real FLS solution p = np.array([1, -1, 1, -1]) @@ -1128,7 +1128,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 - D1 - H1, D2 - D1 + H2 - H1], axis=-1) - f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erfint(q*s)) + f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) elif imgSource: # Image FLS solution p = np.array([1, -1, 1, -1]) @@ -1137,7 +1137,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erfint(q*s)) + f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) else: # No heat source f = lambda s: np.zeros(np.broadcast_shapes( @@ -1304,7 +1304,7 @@ def _finite_line_source_equivalent_boreholes_integrand(dis, wDis, H1, D1, H2, D2 D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erfint(q*s)) + f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erf_int(q * s)) elif reaSource: # Real FLS solution p = np.array([1, -1, 1, -1]) @@ -1313,7 +1313,7 @@ def _finite_line_source_equivalent_boreholes_integrand(dis, wDis, H1, D1, H2, D2 D2 - D1 - H1, D2 - D1 + H2 - H1], axis=-1) - f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erfint(q*s)) + f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erf_int(q * s)) elif imgSource: # Image FLS solution p = np.array([1, -1, 1, -1]) @@ -1322,7 +1322,7 @@ def _finite_line_source_equivalent_boreholes_integrand(dis, wDis, H1, D1, H2, D2 D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erfint(q*s)) + f = lambda s: s**-2 * (np.exp(-dis**2*s**2) @ wDis).T * np.inner(p, erf_int(q * s)) else: # No heat source f = lambda s: np.zeros(np.broadcast_shapes( diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 0cf6690a..ec1c2f82 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -22,7 +22,10 @@ def cardinal_point(direction): return compass[direction] -def erfint(x): +sqrt_pi = 1 / np.sqrt(np.pi) + + +def erf_int(x: np.ndarray): """ Integral of the error function. @@ -37,7 +40,29 @@ def erfint(x): Integral of the error function. """ - return x * erf(x) - 1.0 / np.sqrt(np.pi) * (1.0 - np.exp(-x**2)) + + abs_x = np.abs(x) + y_new = abs_x-sqrt_pi + abs_2 = abs_x[abs_x < 4.5] + + y_new[abs_x < 4.5] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi#(0.000394626933*abs_2+0.997394389926)*abs_2-0.559959571899 + + #abs_2 = x[abs_x < 2.5] + #y_new[abs_x < 2.5] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) / np.sqrt(np.pi) #(((((-0.008064236467690 * abs_2 + 0.062357190155) * abs_2 - + # 0.158413379481) * + # abs_2 + 0.033117020491) * abs_2 + 0.556519451607) * + # abs_2 +0.000535001984) * abs_2 + #y_new[abs_x < 4] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) / np.sqrt(np.pi) #(((-0.123_560*abs_2+0.603_273)*abs_2+0.003_809)*abs_2) + + #y = x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) + # assert np.allclose(y, y_new, rtol=0.000_001) + return y_new + + +def erf_approx(abs_x: np.ndarray, exp_x2: np.ndarray) -> np.ndarray: + return abs_x * erf(abs_x) + t = 1/(1+0.3275911*abs_x) + return abs_x*(1-(((((1.061405429*t-1.453152027)*t+1.42141374)*t-0.284496736)*t+0.254829592)*t)*exp_x2) def exp1(x): diff --git a/pyproject.toml b/pyproject.toml index 0a9589bb..32d1323a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -addopts = "--cov=pygfunction" +# addopts = "--cov=pygfunction" testpaths = [ "tests", ] diff --git a/tests/test_erf_int.py b/tests/test_erf_int.py new file mode 100644 index 00000000..23917b63 --- /dev/null +++ b/tests/test_erf_int.py @@ -0,0 +1,46 @@ +from pygfunction.utilities import erf_int +import numpy as np +from scipy.special import erf +from time import perf_counter_ns + + +def test_erf_int_error(): + print(f'test of erf error and performance') + x = np.arange(-4.5, 4.5, 0.01) + tic = perf_counter_ns() + y_new = erf_int(x) + toc = perf_counter_ns() + dt_new1 = toc-tic + tic = perf_counter_ns() + y = x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) + toc = perf_counter_ns() + dt_old1 = toc - tic + assert np.allclose(y, y_new, rtol=0.000_001) + + print(f'new time {dt_new1 / 1_000_000} ms; old time { dt_old1 / 1_000_000} ms') + + x = np.arange(-500, 500, 5) + tic = perf_counter_ns() + y_new = erf_int(x) + toc = perf_counter_ns() + dt_new2 = toc - tic + tic = perf_counter_ns() + y = x * erf(x) - (1.0 - np.exp(-x * x)) / np.sqrt(np.pi) + toc = perf_counter_ns() + dt_old2 = toc - tic + assert np.allclose(y, y_new, rtol=0.000_000_01) + + print(f'new time {dt_new2 / 1_000_000} ms; old time {dt_old2 / 1_000_000} ms') + + x = np.arange(-500, 500, 0.01) + tic = perf_counter_ns() + y_new = erf_int(x) + toc = perf_counter_ns() + dt_new3 = toc - tic + tic = perf_counter_ns() + y = x * erf(x) - (1.0 - np.exp(-x * x)) / np.sqrt(np.pi) + toc = perf_counter_ns() + dt_old3 = toc - tic + assert np.allclose(y, y_new, rtol=0.000_001) + + print(f'new time {dt_new3/1_000_000} ms; old time {dt_old3/1_000_000} ms') From 6c721fcdef8184eb5a25232f9a44845de04b0ca4 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 4 Nov 2022 22:47:25 +0100 Subject: [PATCH 04/11] test erf --- examples/profil.prof | Bin 86348 -> 86245 bytes pygfunction/boreholes.py | 4 ++-- pygfunction/gfunction.py | 4 ++++ pygfunction/utilities.py | 31 +++++++++++++++++++------------ tests/test_erf_int.py | 14 +++++++------- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/examples/profil.prof b/examples/profil.prof index 5ed9d857da62490212c3baa69ef9eec5639bac6f..5849895121869cbc602c456ecaa5bac0f6c90d2b 100644 GIT binary patch literal 86245 zcmb?EcVJXS^OS@Vx}g^dy_eAIJ$moGdR&sr%Y!5r-X%Z?O}d~o>7r7UCLjt(5l}h; zB1k_#dO4~zk$~`<*?qhF-tBSzzVG7?A8T%Qc6N4lc6N4l_T{SdsYMz@>#pI>_%JOl z!KEk0Y7v#~5iVDvJ5hHe*t}KKq)Fq+VvlzYwa2-ll470Zg1tfTH$50Ws`AZ|*{3i4 zZsUcNRq6F~@QYRhzMh=4iXB>rv%3?Vk>!F&y|+}ZYE<^N#6~mO?OME+Xt$ROHdQ+H zs>!*pdtSGx_oDAkSn(>2!dmohz)Csosx#4`)E?`K9As%xs?1xA!pa#Pk%N;o9lMwv z{$I}pA9eiJi-$+*L5f-=>#bJC_JOH*xXmamG&3{c+KIH4o4en#CH z>vXt*P(mXmbkFQrRlZ;M(xy(kziILF{lJ-WbS%7Uu@11)IgGH=OlOTD!*+K61lXDD zOiKL|bG$Y^4*)|~kPJ%8s@^?1WsG@bQ)fL~yDWImQ$fqu2!ljhMyT8MoP*uvf3c~o zz(z=WogV1OZjaXDqwEg&FV^LVvT%*n;8j_-^-ydow{KIezums@@fnXzee=bJ0S#N{ zQBw3UgfaI}g5fv|BhDheI3LAao4o1oy*9POcN;%XbMk@B4^9b&;q>kYbe?pP5q>yw z&Zd@7D&E*{yXp@m?Yaquq13Y?nb|0rD3NruJ6_En@2niGsMo56ephK_Zs_`Z2xIO# z9n%*!^`E$oGtNH9uE>K+W#vzKaP_>{R6g3Q5QZ_EZya; z*RQ+Vsk{4ib46(V8Yd()b0j+YwR1%}V*7Q{U8?SgbNB0%oEYPZH~y&}UbP>nntpLf zv58uO?uvA}-CDfbkInhY3CZ5H(6}d41FA!8(6b6i5`I)Vy^h$_#IZ9UKPd4SrdMjW zcSl8}3#Rn2L?D8;SrOR~BtIu6^AZl@yt% zx#E2&AdOQBxCl}W@enBRq#fl2;?ks+Y7mj6#U@tL;=|&c!1Smv^8Z@AyE5>XsCl~` z80tkGuGhj2eZ0N%v01S&lXkAop8Le*v*2*t%vdo<0iKo)v^ar21H~Kfh_z{Ps9CZ&hdCp;Hl=F@!Au9~gdmG| z?kRRIr+tW1k8ru2cIcPep`!jT2|wsfu}`0sm^f^7K2U$Gz}4A&T;j45EoP{4V264B z(aH5Q)5t&LB47iY+80}w`m z=A=4YC|H5jNs|HUWG0H2F2i0jrQ2Z#DJ(1jtiZUVIbjCM`kiVO&{V8|3?bb*j@`Td>+}Fwy4Mv*y?P>xBe^%)ue`;uHY$59|%cFwg;La(FW$ z-F@L>kb8sCeFJc}fxiRc`14y}IOB2L^sWeRrnhRd`S-jvUV%yZX|J^_zAFn08wjtJ z>k%+@d0G!QyaY`^8C(d)dImJl^gqXfmV}gkm zyD}ni%9ja39?#)Q{$>@m^gx@ZBm8A98X3~i|zZ>Anp zlbfO9aLlH@j6E^!_@5VGSR;JdCBblglnt?FuE?Y~&}!~}Ew#i}NfG@LlGW&>cw$-f8{&)~LQF8XmguaM z;D{UqE1$ccJ5ozX?q^5a%$}5}#exN*t;UH;?0mCEqT2yjHy8zaHPR_B&}*nZy>q4F zQ+|OhN%IB=m(-Xth+7rJ9?%<*PHB4py|y1dYR{gl*Y7OdNl`nV`*L`n8g5|JAc9ld zjWGC8>-H%cw(7Tzikh{CH_h*@x+}8AT~<1E#Oz#WZ0h{{ho83HJ4lf=F5N7hx2auL z%yW-d`zx}>^}MckVMEu0HZ|L+adq}Bct$iXQHtGwCRq5B;*o3iBrQI%dNt6C#8B7a zH)96|R&tkXvt6+jpWFBn*&61xC!0Mo)&cfzbh14m*}5mD!+Oo8)*Y6xEApe;wsOYW zIuI-7vk!5^YEh0vr#(>vV&Zn{e#2qlO41|IC=z|ko4H>$5UuRn^aU(rFh!ciBaF_@uu7c0)eSF$}jv0Z?%Q#v=g+;qHW3&1&UjIx)58 z=Fh>t-M4mZ{@Wh#Z|LY_u&`Z%;kZ9W+#!@HAdZ0A8w!8Yr^83>pWoa3{-|d#{U5%_ zaCl@-!pjfw=dt*aGzztTHrdn_jiS}#_46o(@O$S1I9Pau^_m+^e3$^q2`C%F(BNAb z{dMWw#VfVp>(u< z&_7RpK5OjJF5TV&dfo!OKu&8%BqXd+?;2=NFu)Pvh7RGrm2O_?&JjeoNXcd|#^y~2 z9qNDtn#Y-X)b7~S;@J=8j=27k@5N4o7|5cyru?0;>ofnx{CAhR@+WFaA4U5w6jPHm zt>#|52wuWwOF~Niy0@I@|7%ohS%_QG*wvWH3R?0_WZUN8qPbJ<*Txm57E$z8zLay9 z%NO}uWpKUD!-rQx)H=!ra6?Emovc@EYNfa`X*OkqjS@^FSUu8cxVO?f`{I}28P~P< zam@*}!&VHIxp>(1+lPWrH~~h@+#X+T*_xUn_d$Z;ZQz2aC;Ijw?>V>a%FO$)Y1#4X z@dX24fn_K0MS|ftajn#od~tHkzNXLbowG-Su9b00FdQdt*>&_wTl%hlnCH>0Qn1i3 zTXnm4pPscq@+3MX7>*P7%X&v&oVA8N{i)y7zrc7n95%V(>#RbQUcw-Q!x^{!l8rCb z;;jq>d(4w9Qg@=+K=;R%K5(te3Vt?R>?gyf*d(R{dR}ye_JODaqE)F|`Ke8v_d|T$ z_T6D~WZ7^bpen&|iqXTNxAx@=^~Yw1wSW2WYHx1$1)L2L#yp)Q7>+Y1ECOt`7juQ% zc&w13qpGpsWIw5_z;Twtg(WKv2dZ+MAQr~*EjugVM8tJ~Oe}qcZ$k{saHD-Mm|Phf zA}$}W{T!A?v?ATx=S+HIQ;VjZ^<~rF+WC_}35NFomqz_lB*Z+Usdp1xEm6wUuUqY` z<`1U?!*QaWq>raK#R9>t(oToInpi6hY{3~b+M!hEHWo$VoP^U9qAD$`oP>ZkhzRtp zv_YYf^M{fTydis7k(xM%@ID#948Ua-WR|3FroM^M@pZntEhrPQd z_1pI8g2I4vobTvKFn}|)(SLt3;)saEqaY31@np1~=gAiK2UPavl;0E%N3NP~k!VZ+2#oMFXy0!hR2YeOz|!~4iQ-xAXUOa;g$3Y zP~FnVq9>oPD)u7;1yCPoDw+HQzB*Wf5PlHhmfDc&@HViHe#y5D?(02$q)$kzmv69* zcX>2woYndsObSj)1mFO6vJT*XfpTweB%h3;S+OuM4v#XA@cN$|-fRM-U0fxiSxa!ZP-j2x>>>b==@k?Y zRf0j5w6e0~KN)rVP^V*%?u>?29><{+5=IxHjpPE&EcO#Q&hhyO-2=(z#7!krN9 zk?8DaADrZff`fIke8(Uynb=>-1qGr9yWi)3rw1k_oMS|~bSI55ap<~(@MSjAk_93> z;8+^=Y>BWAXhT7jl8;(zaJ~%3-;_~!0on6@1G}Bnx3kj|yFCoMiopkNoA*0FnQB29 z6bL0s^J}Q$eSxF|Yw_{uQPCc`fC&7k1r`nMk|*;Bg%@zXq(kRTdibOoHG%@wU~On5 zNFj^vbVHyG^_d2HS3FU;i&S900o{5F<`J^prMBSBp-E5*@nYr6{w-8gX8mzpx!k^Ta0}2${cCM(Rmi+3Mu-^~$f(ohq-qkF?!#pmzjP`60=;z6R?!0)6 zS`ix??uBNNk3fo#0Txg}nAfTZn13;@Sf?8`(mE9P>~^K6)|u0C+Y8FNxsPRD5VtwZ`LC_SlvedG_BDii?_- zs8K=(5<(fBpw2WG5;_B0E{82Q-jXil+0$Z*I=<`hw4JB6LqhMsuNuZic*@%oW1M=N zBNoCY+zGCD2rPiU;)I;`tRD5V>NIg^T`PgA7R{_}PmGYMvlhJSr~JLM2sz2Hu_eCPs|WQ9SiTWvbz-;mM-8*;X1h z9NJO%8~LceH7ew~Ql){S*3UF2wAJ+%qz5hW=P0ZfLh41)Bh*0EkWsvv-UjNzntlsq zy&aT|SPVr`6MI!CxjJn((o8J;?0tlcde81~Q@e?UEueBhucY1yt3M02b=ndGu>kiD zPu}(Y<@H}DE*aj4OkP~L$$_>fuicS|f%l2_@F?61!EPPgEoi*q3PnD~-oBv13l=-a z6CyCcv<^@WS&uTOH^WbJ_`se@$Y{yB9V^t|P(o3|lU{A_Fue(3Jy|Wz;lvJhyJEq` z5kzznRZKNTbRTvlKhzj~@A~fA>6@S?5I(6A4G7yRN5&?>{MRenV>GAk&?94#E62yf z5uihhM@NlVtysXL+AHs)CYQ!OBBvEdu+|XQh}>W?t3!b~P79+8;LiB zYZFfFD@fQRdfig;d=cGJ8K+!j zI|)vw0-O|wUL*(|iFFd=#kw7^`s$=MIgLrEqK830U=}&BPdFRZF<%b8(x?@vn*+aB zOse^QF+wBZWK$f<+~|9Pu%F>hyJHwcKO`mr5x^9z`xXSeK}4WOQ>F;`Oa;&WmN5aQ zt*N%fzl9c z!g+Ik#Rp2YhKjoL`Gs{2?so_OyTcc!1jBK5M$6ZQi36_=o=3Bf$X9)V}XZ&k$1zZzBr3(OVky0h}#iXbTCA z6LCe%O|RBZzPEeeBpMPq9=PDe6$BzNunumlL1+ThLav|<;mwEYrw4M;Y%mmmUSNB- zTwN7)?Za%p^?URI=ZeWXkYG5@-;k2R)IH@U!Bxb$V38iJ9kfY4>i+5BLn{@lqNtA! zEDNdm0!;F925}xl6lxzry&2~EX2-_j0(&zKc?JHl7PYfhZTL+s#i*|JgC^iuOlmxc z_&2DNW6s%4J1)-3s;Fz4d@%4*?E)t23To>!C})HhrD#T1@89}VP}D&e9r0Z%QX7X` z2zow*HxnnpGcFNF4TTRjd1-2{d9t-+{#LL*>GhNC(vK+PRG|if;W)9!`bVW!SQ&M!8lBzF8_z$lv?^Nba3q|eK|7wXH>3iZf zfb_Qq7)B7BO8X1jqof3&3)`XFhi6#|g*1CwRkXtyo0FJod}{>m#9;jktcM@9Y|Vj* zJ4zH#jH}F51cN7Iy$rNLXevMz-qb8K@{Kt`NJGA<3l`Wemu?lVs8|2m_VV|c5g-@K z;qL}pDfto%$BAU|8jR|ZBUfE_IFwp7cbZ$FM1vG!>i&KI@oM;5-lkxKr z!KoDh{N$sKiqDqo!hM*J-!7SQyxgdBH5I^h{HQZ_wmz{q=Cw^tyJcBx zcTz51r#IHgOx(iY0>$2ly#x=tt3PwV>IIvEc%`SnAqa8MRX1drfGY66#$JqG33(50 z{WvqWt&muQS5e7X5>$c94&XQ{hBdY$W%ObJ+*KKn?VA%9oCSG}GPwK8>UN^z0^1?p z=;>H*C)SH^_{F2M<>79~$`LyYSL}igC(5@rxH-xg?}{Jp)LlTFu;$v5TgeczMt7Qj zaaQj_>p25luyQBG0dZTgkzLqG=&Mrm!_L9VcOc~8$`J*ki2^4A&;nrK{!Nq?$s)tF zjVL$xQF9EM(5&W|CJHa47;@%qj@7agW0PMDhrta{m5V6es?ra@2P{qj-?-vubxUp< z06yVUrB|)ElT4-+kslqmAn9q3E@F=kV~;kqt5AN@kDy;@*j{hWw)Dt@^p5ysU>R`U zh;%%3;6b`l!EoDhUgm<+n%qyv!J{d7ESy(phe3wON7eh^nzQ6GoJc>s7Bg+lE1iQU zg#h*-2N`@*&>_!r@u=CRO0Eq>IDbiW0Nm!r8D_R$&2BSiq5f^sCEvBFtbkk=Cgmn% zm3eY9Fb>aIi&IS}@Miq){gkoWS85JmRi|)w>GuW;Ehr|u1jBH8ci~8f;;|7qW;CmN z)9j~FpHplqD_HyR1MP#*77UETv-Whx6i36aj&|Y05D13j zEROmux1A|PODBkGr23JCidkaDge+~L;~?~@1kW2pC9D(pU=#kY%M2OySq{7#(ztQC zK3m}`8e69YP6>wN?2ettLl5szOIv#(6gG9&tY9(0E39r|COH zbH7bZ9{=_9S_b*y z*hiQK){dZoK&^xi(jLlfai^(IieHzV1ks z(J=@B&)Y)NQYY}i1U1F~>6yCQPtUHWsNWU(q0*sPyyaqyfxs!jaGb9x{a}Dv=@+&r zj}T!6%S_OZQ2GPF^S02|rSRs^Q-12PedhZWKHnHD=odI87>?6{25}BNyRgu2GL8!c z5ACt*1911Tf|dSX3H^cKd0S|F>jXa71fTtdn&Huh?Gnf>4<0=uis4-d z1jBJA;3gxd9d{Yl4%EY0j31S5I1bx*!P)_gx&U1o0G>C7I~tw9oAV4&TNUDh z-ttf3{FluEfm4FvI4farSWbI9oRL~OVDe#?ZU0B#d!1qNX9Y_KJV96&wq&?NAs-IM z3%s>(XiYCfw2-zB;qjxsXi&5Lv_~x!wZV;wElZ?!f*vfQT=@e2p?<)GIt}Y~`@AT) zxZ;0q(s+Cc#ptA|*}HSH?Z5UiUhG6=%AU}a?J&yfsBP2fs`I8ovibJ`(}5yC{wv+Ike1->b}75v9H+NNI^c`}SR zx`=Qs!}`G>_CE7hs=-?H_QM+a;I^QaV25)cIHtGLPzuP500K%p>(ssOny|iL+g^R) zm}+X}+})!z(JH2mfL0L)=vzEKdP@!7YBbh>?2D#WFg1<(|!GvjPlYewZ-Ynv(R7ziggg$IX2y968>d7-3=5%fh)6J2 zoRY`x;L>Kx&+Gy{cE8WZhtt)DE7!ooJ0O$VwJac*#Ypr!1f^l@wQ+$!ih&nul$&fU zyo(`wfvYe%Kn&9I;QYgn+Wwca6RM;Gcj4SspN?EIv$w*)_vL`L^S`Za=&)##XN;jI zAea)C(w_!5KD0?wx+ZpHNgbVZ_!EG9x(yDOSk$cBlQzMT3u(gvmXW@?1Cr zB^WEui7U_Snxn=FWL!OU{ph11_ z97*xo;3VRhBsZxo@*~x?wk0{Z=%E9gpuYU$&m(yrHU|E5zSfYq-iS)bLm=)lAVJlZ3O2gVFF$>$%$1XvFeGJ`_FvQ8>;3$((sqv zBi{Fs63Mlc<%b{Uq`wOB%bQQ<%N$piWZZfef#_7T7_q_qL})Omlv1$K^<;`n!h0f4 zJPQ<4f=6kd$y0sz`EhBm;~k+}K@8jzgAcpU@c5orhnU0Ucd6HG_-gAHj%9_Y>) zwh*UdUdk{Z;lcP&aR-Jcktp)CZ~0Q&via~$a%a(KL~v(yd6}u_XMNJbFczYsA>3IK zj8!yFnjXhKAUg`7C?t>2k%I$+ zDyILNd9@&YP&wmL%m#F;8j47Gha`AwwXZG|%_5ogj*!%H_$8V_EG3{BN*zc*>>BN4SP$DgnFngV(W748EOqW1gHm!ur^Q5wbRXS$lf{u$`(Zv!{ z=5HEOOX>Y_un%#P*T$iCEC0A=b44vvXrQaDGJ-6YL~)C_RC65b$gh51uDr+?A`4#VU9auRme7q|DYXm22LG3 z8J(I67ei16aERzQQ5ndssG)Or|N7)n8-+=i0HF~X5HCQN!e%I4(SSf}POw5Nn93Be z3bjrzS+BFH>}*;uPbEu$aBnlc!1c_gW5A3>KIy^HHkO?8Z+6{`yp!O0Prgb8wZ-uY zRe})b(RX3rVr%wyVUxmp4}{bc>I7CK!DSV@%5ND%bs;1Co;W2mh*j6{uAA=m6)7rp zkZ+f2v|=252Iy;M0t$u69Ms0)N~j_WDFuP#L3ID0$wx>fzudf8H%iXl2_YwrQo|ER z-G~K6@jhib?q2DQ&l(>DohfY(0r%#UOJ(2RUSH8mP>_-cf*+OI4sgqJMZ2)`x4RTq z^vVN| z1cQG<)lvu_tAI-}Zn!m=3_DJ^Kn^dY&@BBhPK%Kk{HVq1oG4dg6Wk4LniVsZ_wZqp zjPEC7RykI%;lnhNvi;3>d8TrWfa``fQz1qBtl2X1oa9+5WOew zw|lOB_#PkRz37;&{{O#+YQ9M-m_|o@v&}F=(kXfZ6*J-6=K7ePp=9;xt>GyYp2Zo1 z!$$87fjuhCi~|OWyeRS&kL7NL1q?Usx`LlZgap&J_t?|8!dZ`j=6Wz=O*VT%PahH_ zmm58?f&lvGjpqi3j&24dP?L>VYt&?8cbZgs&8j*$D{0TsDx;+lJeJP`vTz3hA+di&Rg%m@g$;vA7j#b zkKvkzL5OO>EZuUpzXPG7qdo2C%pTA}k+)%7@lk#7J7_i1s7W_cxnFNHGVK3q(?6tr z6>>(sJMi4b!MRha{k;02O`qTk!qpQ!2m$qJY)^zZaavfCD66HBk8(`NN|gxjCtp2) zOZ0y?Y_1(zg%O2s-)6E{eYt9)Ip}=M{TyR&gjq(@WR$?oV@pwG+h;a z673(xLehZ9@Ye^rE*=2d>}2|?-^P9BR^UCWSMjL5KwO5Zy&Y)%+pyd!O5k zT?h!bhSN^|ad@5r*%ZA2%7l3#=lip-l{?Nu4MTkS z!Ospq_Rq~65Z2uMn)2y}=r)9Zh~7JnPz1>=eJN@);sW$lX|K0@yjV-A@-_DFjI}-wLA3Em(Z4fSw;>%;E(l{X?i^=`jQ2@EpJ3kQqO{ zUH>?oGf-NI7o46>!aGZjTW}Gx;5jH`cFZO#C*AWlAB%Bfq;uISDhp{IGNpz&VA|DHO2;W5r3XEt~P+hrg@=SW8&OC zN}{nZ^_?!%-*|2*1iL@l@l)fJXLv6y*;hg&7~se|&Qs)s)PG5Fs3>rYgwH6z**g8B z^?XvSN*D4~*ykM$bB7i9@nHZM9Lu%PKkX4Vk%6`Vq9`=tiHBLECN%*^*`Lwhm%Q=m z6kv!El@S2ODkAHVfo7bQ)HW^3jewQxRKuQW{%$uFaMr?sFkgd^V35i0^pY{AJfK^5 z;IWRT6r7zyYGi8w2JvE0S4P?1^M6aPrGc|^qk!6p&tgvy)`dO@-v@)^6_76*fE)Tr z6#pM6Uj#hwGb?aj2Y}%?@c;vQpv5^B%PKO=bn39$;Ni-~lSI*1RjQ z2S1V?nBa@#pE6<48<_m8zo0C;rAx;K$e>!tD~eQ7l9hnHYu) znwt)UsmVQihriB|1@_ylU>UfqaLpFpz=(AYgUq%tfO)ZN_)*vQJJhXkL`H=dIH`E( zsPFde{<@}~K{so{?YWAvj=`U<>A)w8i53Zl;ndK2()UuK5H#GbcRTJt6l>&0Z9FOYE$`k^9da8hyZY;o1cNyF5X%#1!kljtYF8=(YfhK0 z7t(dD4LBu7qy%HdDT#8=OW$sJun10}8lN9{y1=EDWE~@ucR%)vjJ!_;~VRMt16Jw1Y7V?@hoZOPuAwMB$THz z>8baI540(kyK$3~U*^vbT9g&^;Q$OE!b)NBZ9LG8k>yRm&V;}*;vhKcfINf{Ibq|Ea7wT?As_S=KkCp~kxM>a5e292`{Pry zpBqF32y1#LBi&gHMMCNY5t=FC{Yx&}8Z;ODuv*PuYADMJD2#5wwUCn;Yw!H*iFn6> z(Jj_Cp0Yox`tk7a6?t$=Nyecj`mlm9`3bB}r9qdFk9uqFcdxo7L@Mh3o^zYtEH#iA zcZAxpL}bWnPWx!0m0mqB49-{b#tH_HU#3jPF`)gCVEFh=K+JGnWoDph;3-Zxo8{-I zteZkOBp8NVzk&o@MPkhIxhtgYyxy1UD{5@EH0$S708J!wmzo;jllUUd$@d!_1MjIM zlhq=f)4#a~rY#dL)~t6o_6ajI!32Z%Brt^`q(!TSmv>QM=q%wUrmWzH3L5SYy=MiP z3YG?&=fmby97HN0t&)`rH>3*1XP8ZtJe!vJR_1yz16aWiCDh3am#{H2bf7Owxe^9*kr7!irx5)4DHp(g{w#W5|Dbu|{V zg$H@Ef@L+9brJ(JUgeuLu!6M*BT&lCDwBb6c-|VG5a|ToJO-s!r=5Ll1!N^TRcY?X z?{}E4*9jUV7>+X>H422vf=aP;z@%NarA^x7TB?jAN-Cd)%G5VuZj9e@VL=Y?d=4}P zY!U=XFdQcmq-R8UGfw^rV1*8S`akJNU(+If))DAuE$z?G$}?MYNn8g_^ zlsYgnYfwPqDciH?HWp7c9IzzlFBAQYCILw20wsMA@Whf zzdhJ3`r%**P^vbrNrzP{iEUFDYH8uPI^|;N6eD8>@dZi@F*~?cFTDBT;C9Uye++#9Ep(TF#WJj;LX>{ z*fWS~%7ozO>EF-K)uV|93C22P5>9$V32!V?KR^0aKc;$2&?&(n&OyldSMY(ov?&pt zl{ez_f}YF!WVshJESB;n06cHA7m&vkjezIJOYA@k^sz5a35Me&0Y8KW^VL=syKamR zMsgY!foNBP;W(F}GJ^Z|_{_K2An?AA24r#2YDOk!NT1Nw$fIBp7|4b}-asUhjr zdkC$u{5>_1L-xo=BP|p^e~FG2%9$@Q1bA>0BHl4k zkHEzF!U~+y7k(*a!LlkVr0At7b)q-yCl14Lirxg9Xc*eHRH^y<;40~Rhj$(LaTKKU zV&f<1lwc62k6fB%f`>{ZbowrA+~F4>6AgUjOoFlEBwRPsNiX3*p84|EE~)+MKtwgr zKs@-tnP6n3=vexkBAgGj?b-cgumjHDyU-~sG#Pu1&3HmSv!Yj|RQdtQ^lg7nLa%~~ zj+f#sD>I!i`{1!e5{3~%G%mj6p>@YqBO9(7xb1VqW1eTTDH`)Z_8%YS?*Oj%(jV$$ zf}Ozn>~uKA>LS4)n+SGXlk}VjZ^pU)(w6Y6D{3j~?2yFT3(}1v3*rLB~`$8zmTy^Gm-thYsyJ z=8uqkkn6}O_`%73;xHVi7jaHP8z7kEz91hpJmuUFPeF(RVg(di&pT?m#i^rXjwo*+-b%ywa z=Ck%Kodyqxvk8aOS+DFnK_A_C`pv`dYr@gR+JUWG)`iG1X%0uN4*u-YDL65oIAHw{ z*W$iPx%7HC;32oASRkF4^4-)o?dvAM1bCCVa*vY(`VfVJ6Tq7W)Dz^^1p~{WlY2^{ z67@WR+nE)RY^+3%2RxafQj{wWQZR$=3Q&tNhaM_mG^9nlEPq|~?>h^cDQfFgmo7)Q zYX=Ok>dO!b266gy*Q~K0~ zi%#%|Q-T4Usa@XHRwB+pUXH=dLxBRg3~2854mbG68XMl*k5Ho90paS-)!B2OxC|4W zhwm|?8-IQa3^G6{gGN$%K$?mh(~E;&yfWHWi4Fo)dc?RZAx7lMob!L==OMg`kcYkCqo) z;C;UCm|^KyJbXo^0yqWH5)5OdMxu<3g-LXPU-V#w6n!QxG-lamKN*)hZg9qFc%H`= zJAspKgipe2v@zEvZ~FTr7}XL^_7jKUbe8dp)90;{6aH{YFdQdgDxDDCSmb{+@J%hf z`9jALgIXx+4^Ex z3u*k}E1ODVoFKx_h$t4@P5LN3KK*@v)8gm*({KqOE_A&Q6-lvmKlSHi=d?y(y&XM2 zvij*|5I@brc?C|IFAy>;JR955{kV8~g?TbX06f*>5T26j<*K;_&eQvNq~jB?L*|H*6f-TL10VtN{bE_T_sKAtN;W5uE2= zhJHMIXwlA!Zl|Iw{H-j@i`w+U%`;&aTMOoh;Rz;j^E7zA`>RX2@~nod$UhW}TYtDM zy^8G9tDr#eTwK;57y46sOE6aJ{e1d_+pzq1-KbZoS^;CJr2&;-ILl>jFz_`%73;xHU1UKr4)!Ut5O==;OkqqlVWqOzin zU9+Nxy?=Ay=XiuM`|%|hhSM8?s|~y}BW?%;YnNfgA|JJRwzexL)NTafq(*_yg4}-h z^;B)bg#}h%YeQ8)=_!bQ?I?WZ0^+gwIzg`a!UBwe|Ky`iSX-i3bYGbAtN>Gwc*pTl zkD+w5EkFSIsHfr$5iEg%rIW)Bvi*!%cH~AAQ(731C=3uTMe5V@(NF_BL<5 z>~ly+ZWN&D)OYY;61UxSH@wWq_WgN4^^m845+I?qt__2zgimlG7=a7H*qt_z60mD` ze!N4^U#1*Ljp0njpWgz5oFZ#U2V_@A>|QVdrSQ)$bR9RmSr734&-)?1ZhLTc2kHTEArZXAp!g>RqpN^+LNNt}_(K}KtBNQ^f z+&k@5q^QA9^0 zSG3j06}se_Ucgk+<}T6pW(?#nT9#|%$v33FFsaP7^gFS z)PG{?IOBIha2P9a!~tLcacU3iULxSDgl~CD%py7wu?}*?h_7sVLWwSlaKcUt|8BgU zX}UMVUzDX+PwtT9Zc{*pwB}ep#ExM|7|n|ou2?dqYRDp9P5%Ui(7dPN%M!~_gOlbR zWdE`BJQmVFM-IX{gI1s?CyAgUfAiA=i6Y*E(b(|}P#G=}KqrJJmYTNL;g$BrS7XlB z%?L6+iq5tGFo<>r@)K#&XCl0rG5k^zy@>^PwizRl4EX5w-e8@9p7;p3cZRUC$NGM5_P;cEpUB;=riL=cQm{wYZ57b8J89O3<~?n$b7tw z?9w^`e~8aQ?@T!*6h2MLkpbKR1J6(0^Au7`$yYFrT>j&3pKve_SQ=7&Bn+@+iu3FP z^e!B|&P|$KfDtJ0`ktLUh-ZgDK$B+R2b=8eNfc;O?G{$c3|@S@E5Z%6%=$#Ch5X~S z^si_i#Wdi8DzaWm9gtuU=OVyFhE@NZ;uK@(33=A>@W4`#xUKvCq$Xc9CBlsG+EcK? zJlUxP#5zG5q9CrzybMcKSyP%5185$=x@j49c5G+78wDvf_0NzTGfoMH<0MHCC7e$? z-(C92K}cT33Vv|1pEwN18RM5unw%^XWIZHV$})i{Np!NGI1I;$Y|~fxvYC3d9>5-Q z`KY?t^WknQ#JBGf4B{l#E82GWKBnCXYa#GdIB0LT8~xe9-$A5V!H*M{{lsAyUhf{j z%XTv2grRN!YSU)k3x^aCMuFoD0K;(V#NkmE)VPIC8gLq+sQY5nt7_bAvFBa}bJ#Ha zP5qMK5q~tyR+1?|ABFSQJOO#4mWNI2R*czH0r)HJW+fPovoD_d8V;2YkS7~pY}7$b z-mL1s1@t^C;G`xr3`6yn5gr0I@}?m8Tk7yO5}*xuGQ|#Wd4mn*U5?Fc!DiOSj6YaC z87=}vmC3iWzzhhSC}(ytmy%q}_6W@l>s)2HbA-9IW1Z3D9U4UT7L1&T#YjXZVj@5- z;9zB3;qYVlY#`?eb>3ViHWBGWZM?f5KIOc7L8RrGO>=_M)>3FD0KB`);{QW&uA$XP*_}J|y)PVg@ zI|TbYJjy2@b?o=!bKboT=US}5h!F!!cb1UWhZQ(6h?pt8vB*~Z7>O<*pp9m{2m-;} zO*#)@+f!kdTc^6{KsPUrv^O>f@&93Cy|3^)YtF|3dx+aByEl@)jsV{s|Y$!!moCdHhxh;A_;U*htR zyMxkwhe@>X)ey_#I_u%uWx;zu7U>ysdE69}8D1ETlpM@RVd%~4G9@XqqAtAG?7_6~ zioEKPEKoHh3atZ|syiUy-((iibPy0b@JhJnbFQrVN-PB&LE z5zc6M%M~BSiz8*XjM(#vf$4}tDiUKL z)hs*#g~KWkU(0@@`>hDL6ht#qgag-4`Zg}X@Wzqt`ft9{NWB~IK2bOu`KTE;f+79)`#j3wLv3 z;Vjc4<;>ZlHk@_go5dUO!H8p&9fA}IhBwm=n<>P?7~PPyGS-<0x8P8@L*#c9vR>Rn zK9<_%^t`@l;5ozn8^;vPl%DJ%iENEWJO#m-#AFgZ7m-Q$oHTgJ5+)Eel**tU{m^?6@35pJ&G(913-WrRWl_a#<160Ef zlWOSngPe9zF1&OF&tsAYxGhcU8fMiDhO0QY#wSfKnFBh1mG(^FmtZ)4O#Ek9Stfc0 zZav$3!JICNTCeofm($x#0-RE+5FS%juy{JH&yEg?oGOGTWo-6YrGbs>R^WL+M9lNL zpYKi!`?Ia0?wAtZ{ZzsbKY2jr9C_J#e+mTUEy(F^eQF53e$IKSU&GFt$Ds6%t(h}D z2WcpA*WRjODmv(q`4)ytFq|Pn0aVxM3MW#Ywiq3A^Ou@)6!r5OF-?1ST_nbJJC18+ z*aXmYQ_xC)TD%pkrQe~H3;6dt1bN-ww?xJ&7~BY81t1wEvCZvfRNBfJ^E1kdr~ ztwZjDiGZxWQJBeBw6y5^kH-GC51gv$$Gyq)e!Upr5nhnDV3%MJJ2`n4L5Ak#l%6!g z!ZPpq*a2U*!C8n02!K;48VQEu48cXhI6D^b^qLo3nqUP#IN47ehU3iQ7bi`_1aCaB zf*+jhCl14LX7!7c#jJ|#)pFk@7>+ZWU!0S+_5O8y0=)ah3V!HhKXDk2GrM1$EMFT> z+~xg>+vpht{DGzMMQuXz%`j7BSg9O8Ac!1`Yqz348gj{*7!0#Lg z{<5Dq49AJt+w@PUN*C}nksm}Tw7zoc*RTZ}{P9YH;W*QQXTp<9iqvt&f8R$O;t~#R7}NH20!ZJY$3BhxB)o?Pi6Y2-H!NN;I#^4|0ULMxsMDsj{$=}@o0Y_x*KUgKVaP0Dgx6 zFuXM+U(bZ_=I%ayJi#-jK!3Q;nl-7{mATUaC*`dcitXaZ$eIdUlPO{Dka?+)evF2D zX)zGwLO$xE5?8_>$MlBxeOHzqI&Je*!G1i`*N5P6nT3q!F72CT!?B}UwBRy|sGgQ| z%cNN)c;2neh+mp|^cv1rHkB3NN(4?N^Qz8%;xHWNR=+r@H|uAcT4Ttto!vjd=q(AS z1jBKP06#MyXpS=ob9p@LtDJGMZGrs~P6>wN#MIGxgs+MScNI;)Cw)mUPFLMOY^wp$ zTY5HMeo8PLCnj3gyHcEDm-y+V-)`M65UHrI+uYi-A!sJxlvH%|*R9Ll{tL|Su+Z@n zd+!;eup3qtaHD}A^(U?S@+S?tD(d;MZPI3M@u3itr&+}?ie~@*Xl3OIYXF`VQsny` z>?aPxyYiP`oXr}<-QHCPaI%6Qoa`qK!*Pn#@n%<2YSGp8p3Q;)#Bz(TYu)O@^V>3= z5)8+A80oA8GYzJQ=zHnH!>3Mq4#^6h6j_pG`cjBumsy_BZ_w<8S0Uf|N9)#4*w7>;wY31=Kmef+5F6KxIJ z&H&FSE2QWuUBm;xaGWBL)XYyRP7o+ZSO40mhU3K4 z=K5gYIo0Ki=ViA%1r_z$_tTTXKXfzH0y<{7HRadP&RIhrUmUk?r`FZuwc=}x?rhMqr&W2 z=C(8o@?e(R2mtqaF+dojL8ixc>0S`R!MeQ~-)m{F^kRKFk4z2awwkaa!J3ML-^fRu z@#ESB#Zspz>fR}t!v?*YAt(+*5(-)BTa8Ds;sW5|1C$2w4VZBAhCKCG8`tNmbwq-dtC&=i0cP=cYM5K7T_#L zaSAUskBC=mw69+0>x?YG*<>o-Jzea6%_n^nbxHrZ_4@6 ztz6KoG$ADz-W42ry#ncq$(&@#EL4^N#t%*jhU3HsDD*+Tlic0hy-xogB5Y%qPT2N3 z6dmJImSqWs


+^d7!uSMZ{TkGe)jftR#c=WL6|&lNbU;85d7m0&ndk#*C|PwIU6 z%cgd2P-N8oj%XelIqroLlwdecaf8H+lZMzp`f`@)$q!BmhU3Jy6l6n$2f`ozM|=Hb zdT*dpQkoJB;v~VD;mDs5_<$mVWyWxH_bPn!)xhi!slG2Z{?VKpn6XuIvPv+BlLQuH z;(BJ>hz;!ctrriE)WO_lg%o`>j-&Z#fc?Z_I8IDdAme;pz3!mp;b2d)f*+jhCl14L zVrajNGfrKV;hUGBO@eLt{YPy2pGxS`DoesHp%I1I;$LH{zH z2aa_7an&qvqOpQMoW3v|r}^TIpp)tcIE`loKRDS>9ERg0fiqIy?cwJtm-q$@8dmUw zll{bDI8O6r9ziFKS$b|W;+inqjwDMG49AI~GBQ7BJP2RmOMVo*@Gxe`w#fspCD~V1ChT{|w_hy_l5qeJccwhm4I3*Z{lRUnmkD>Zg z@Uv*g(!U(71zXY!U4PAJe-hfrkDjO3g20fXsaN4WDCtS9{u!QsaArzp$cz8B`_g=VFk?h*{gi9SEQD&PUtC2*cu z^|)b)Tx}p}E_}MYi z+=g&T>5%%qQ)~B~pCdAAV8IQpGn+5w$?^$+7{f0da>|$Q75nL6bMS?SAIa>wXW|+9 zml)5)l5?P=ZNBR)go^cO_PE}pGaF&}rT04|7|ye7h_gE7nF%LRPeE{fq1{$B-5ta! zEy+|pnP50hG;SE3Vz~{!SGDv%JK&t|*G#S)O&T2roHejrvmq+MaGd0smYS4KfsCngeBeA&1iWY%gd_Y!7(;bzbp{X73kG&1oFZ&niTYfdn8o-aai|9&95A?K0X!YVp zEqVX0bIkhvaAD=%IroBs=N0`uLV}i|KY-vz&7OaBtzS=Hg_Np0FYOugcP=r5@bLkC zE}ghS+B@w%aAb92KX5q?UKY3HmulcPe)qJQJRu0y`pCO<(Iu{$3o1aJntnK>xe6h{Kg@23u2JJKRN$0gI8O0gfEnkmN&U8c zx&Yj*MK=dcxOvGhP6>wN{N5C`C}`l}(%p|N`l#6}+=WYYN-!Mf2ERDz1AwqPvj@2R z;FMrEPVw%Sx$gz@Wgj1v7R=)=T|aDndEY93<1WE)oac~EBXywY`+@J2g)g7M*7acR zL+cx*!zuM4%A0vgNiZDeN1zpDnP<VN`;wrRo-^CePQn;I!~a2dmonxp2=KenB( zDC*!FGp~OT{E1*r02t12d_GN|OcjoRZ#*?ky6~t0iaN8Zn(IdNXZ~LicG*-`@Pm{6#9=s2e3g>E&1W9Y1axat(^HHh32QCyrA~jx z7J)2G`IfEQ^nBG6GA&56V{siGM<=f^iORN2yL7;Q*bg+=GU4yz+t5)O1w_Iu!Ens@w2!`uPB@^)aDa`P zpTG3Fou%N8R`~pauXnwI;wwCCO!<#-@D$q_oYV)3FcV8`?49{hiDO~k+ES@MC z8E6B*aNOH*7EyXlX(Xw>1WSz-7|uX294FyD*%}6Gzvlot@uP;7X_x=kL(AdCmg?85 z*Ld*<&}r-q1Hf>c2_Or=1B@1TLqMDhFXjl|otk>L+_;U66m@!u@J0JOcN07a0K;*U z`SA)GU^p)WQ`F7RgRU=kKxXc$H>#DqxFcU9lw%?{!qXF^%>hvIQ9sGCVP4z{h_IdW z`KV6$X69|gz=e||$P0`}qfY=4RcR(?aEfHe(C6^FeH#@9E_@B<OD&N#J)}zL z`RDnZ*J>VCj6pUHNaODHlUt66Q`C%OCY0&;^eRzTB+u|VAQDcj+({8p+7Qg1jVYmZ z2>68#i!1fs0ZVUosxTPx_Kymy(x-Gz(C&sH1%P2pB)N?BP*itj;T--+|E0t4URKoa zA66-oO{YvGH04F1&S}kkTrC`qzU~2c$VXkder@XJ(ib7Oa+d0&DwN2B93^$}*=*ms z;WeRdb{q}-s8v_hEc@)|@4+Qo^j6DkLru)UhV_q7O9Y$R`aI{Q%8R!rf>zmMTi&NE zE^S8Vze1e&QOn#KKJ%x6aOySik^7U&RbQc1#rdmGB8o?A1uP&R_3O21=3L$bJLCb= z_5?Stla)9+IQ{w*N`fHy=--)IxYD%;H`$Uc*V>P|%7xf8#-XoD%?~>V9`Uq$`|p_Y z@%P}fBh7mMM8TQ{W%ZZjH$}^|YXvC`y;(orUhy&!gYx(@@yJaga;}pkM)*BM5NJdR7fxkCq2lkNc$vk(o<^4oD@Fs*S^P2Loz0yK_u8K zD&2mJNP^sHaP_gWbC@#{`x5{cwp|&!0%^M86%a^57wL4no8!YWuuzGL^ptBMJmmZB z_JxnnEClQGSKp4YpEwLhJ^*Drdy}N-Xs6!I zCl14KdT%1F*_%0|9Z9i?{#Zfx8CxNi;(E@(?($pxk<5PLFbuuc7Nsd`*96DVcz^t$ zJM}#_nHE9pGG7Y-bgGqlHJ}3;=tn*{q;c-G(YGi7><5ApqB-l-volKxUo1b|_Pb=)p^vbS*QgFq-c!4oX8KFXJ_)PLAiR`4@;>?aPx z(W52q$=oa{p%p|#`CN9EB+3tF35H?zo`g6#EDeeuloAX_Nr=tSI09azaYQC| z@v{N=IcA9g(htWc$B0Z`$$YZj&eF z{aKyhp-Q8G7T#Yl$og~q{0$%24~X3#1LN=(QuI1tZGmXi#=QZ4RC<*qkD}7EGaIpr z@hL6xE|~rV;mx_X*?YU{rIF3jy~^AW_XdSwvXF;b^gOsPG9L?558-i}@igE+#4pT8 zNUx6QgGokl@=!5t;J zWr}wx^#_Cm(+=tA{oIb^7U1U?*fnW->^hp5oRg4Rkq?eTSK}PR{zWpp#gUHRaRU`aAkh-f@Q=~E1BuTlqLT{piaC` z_))j3p?MEq0}mA|Z~}oxhIgz+mo||(TERsZR*)}a@cYB8U?tGRKsP?Q`Y-f((E*{K zLHerJ`D57cMZmqz3Vx;*weVmg$O`h?a|h>6srK_~7zb9+W2n>$zao8Z4P<~7EM3ss z5-~L0x_#L3$(N&#lm!>Vnc7qJy-6U35>5%mf-|)|A2*!x|H2eK7YGaasDo-Xuhn7+ zNDM3BgddMHtQ(A)ev3+&Flp&hu+wH3DLZW}-$7-Z4fs);%s#np-M0`K%L-OX#Q;Vo z#lb_||3>RBq!mBvl}7jb?Wht7FDx4cE?@y*tc{oBY>IOIn`oP!iYtF4Ws$afqN1`w zihhj@dLS5U%VJ_hLvn*eXZ*kQJ3k68evI2t5CcfBMWOZCkF;*GY?;|%yivH%CZ}HJ zk;AOOPA=lGCHxqJP<%vs2sA@4{@vivcPTloyBj#)A-LM&>aRWxo z(A5E8ES*lRz$I0OEFQ7{(zbRzFHBkdsE%H}YaLAs{z_KhGx~f67?Z#0jP8r$gCBKG z`8#9RXND9etRRc_@qR02&OHP!H&y^0L6$(wf1zKYtFTb=X}2tE?M?!|vV#601rZwZ zyJkJ&?pKFfd8}aVywDMl`qk<9x0$LJL^|+egja$BrvaD6iHi#L8Fi&igpy_Z%}_NKhy^j6)6W75v^G`4TZbxZ&I>ji+@0(HUd?UNuxzWbD zES}J!*#Kbbvx44;4zkF1H|CoP-Fw+X6_pj(^*W&(XhU!pjulw=U_B%kKZak#!<;A{ zn{3n2#rk{ZyZN3gKe296o7v&>O~UGStgE9fh!-WGEuB3+#0NIS45Sivee)wmXL!;{vLF=&xFqy9x1 z=sS@b{HU>2V@7Pt(-5vq7zM6~0>D^F5SGRN!yNRsMt1&%Vs^94DO-{0(0Nu!(a%!` ziBTz-rCZMSci_PQR*>HX8oT_^q7%nKT3NwDonDo$4I+Ak?%vNZ`}R*zy~dIh0LFqd zRcxT#Lm@fuFsJ+97)~Y{evG8{5I9PsSY8Ru9{|SMykK}DJY@cFO$$@5W7j-UdB#8( z0W0va4gh0qx(XLJ*nG#k6I||pZGQyFGEF-krA+<0)lN{otN>Olumset5O%^mVhr~Sr)O1Wbh_nrn zJr&-nzwVm5Y$G;D?!lXd}wK)uwim=gqpoFxhYC$3jHyYC0x!T7*> zQ!bDmvP(<&KM0^GFECK9MYnX}ogO#+-0tcfK)L|{O4nECOQSU0ry0 z{(R4{C4<3U3*fSYOd^wo)t?+wM|mpjYt_FT%0o)r|qBq$vPA$39eW@iXyld~A`B`QEJTMReAXl7yl$m%_0?5r{Q8b?cfvCch9FcT-AlrT)~S{;b)!{hj&GHiEBs0F!UK&@)zkDqKZXW<8*nOYgv6&wD1Mx52D^Z+ zzn%T3>rg<|MoMeVA&k3;ZJi`jp;M#5DfO(Z?9K%-!TZ4E9r6iSET116tr*&-&)B%y$tZS-@5v3)UNn!fE&dHK!r&c@VnM#16*{DwGFc^ek~HGNc{?s0X3QS2pa zENM1;SnK9l6Pw4?_l#oYO54AfdCwiSuS0mP+s7<;`TX1B>I|dUL(9agk?pF^GK%Hq z_<^)7=5=^#9KK=w)jbdg27d{XI!3in z;SI|lP-FKG-QwP%lv)e@%QEBY8Ah=U@p}%e;I%okw+*RN6j#q_l#oPy|264ds!oSv zz&*|8rJl(hN&4^efutyXmwKJi?ddT1{ESOe4>v|^`-!!d4?a;GS9dXrB~l3<?ug0YI8BIL zy@J=S5!&{t@TTmZKek^pY~C-n#@deP`3&-xXCwfV-bHN=UQ$?$|*y&vYt?dzqVeb$es| zqPVJul>$xMjcmA&Z*O|HMf&pkadjo5*mFlD{6QSnGck2Fqgc^duh(vzb^J>laHl^X z^5CCp#1a&8J$~Eml}p~j6y}#QT^7JNKP!6zF(B{KId{2ch~E8l>#R}mNc~Fy&`owD ztz;2Ci|{QAzfe>J91r9z%=+MTt%qM@BCP(2t*xO?7XURj#e}l`Oc$PgyNmg}F85{E zxSSEgWhEPi(HWyK9-nT1U3m9R>zdP~ZFA^F1E55`$ctG4PUJQ&E3ZY^@Y)vVp;Dg; zuh+KRQ*9c8g%co2i>$4M8b|<4$sGK#dVvb62B((oAQM4VRH><rd&C{b(l;`x+%Ll<6?*E9MKX`d4}fGnOi!*8a0 z8q$8V(-|%m0R+7T4eYu^^rOAzAKc#y=mH43Hl1k2=$5Nw5hU&-0R;VPqTd@dddQUN zk`|kG4%vGfobv)e(6!301J~XWKiCk9g{^o~3836}^c7Fun_Ckn`R)LSQi?UDfT?@7 zs&E}jhftrdrad?Lv7I|UI@k+wCjx->9K}-#<(;mgeE@V3#5AgnU`0CANNp1c1*;aY zD-T?%_Mu?2ZJ8j-{E=dqj234nZlKX}Vdvf2p^cLY^T&i9E_G zz=<|nk(wJh+_IG)rRLf+O;mU<6|b#-)zBVs1Bk9Cl-l0Gi8QW;5%q^1D_Fw}LHuX$ zww%`B*cITros4x3PNdGVm~&pGrR8RiPfH{37P8g~_GY8>!bYtpw8p=Vy?S+S-}8pn z?Kf%5f$w$OmK@_;Ox zdAzo=j&UFn@5_SA*ITxtfEEK>*WW1vtNcf;aF&}sMhnbrjgDNdMD0&()xn9jdQGw+A{Rm7$h3m|9KVn) zI4L2GD4?O!fXPXd88pY!0&D{h(uzD>eG5r}LD$$*ftjdd!-Z-4lDN~BHU4h*G?Hk_nDdJuV zK-8hTtn~xcxDOTJNBeMfxqZNnOoK0cuHF`Q%A8!mC8|FksCJqhN>T$7Q5<#3FLkRB zuDGeV3q~NIa}l}c)3jL8AtwgKBTE320-zNw4Hbd5B&-Pzl08@ye#VCt*W>s?u_&%S zIUm!HEnrcYrWXlKFN#S#Cwu3(N~t@zA&cUiX!07WprPn2S}QQx2*IxK^HSMsIy-9+ zeybmRmPxbwIAdj_btT1+sS66XwQuxUl`epy zFp>}`S?2NkqWV8?uL-$}0BS4myt>pV72bVwe>l+jmMRdO1pp?c1W0VN_fJN*RygdL z1atuey_Eju7^7Pes|gz>z{1LEGEIt>pikj9l$~;S)K4;sT}1aVna(J7k2?#e`ZJ0p zSuyxGmjCFNrkodOr&9W998b1UKAy_GPc13nX_cH5Lf5^~hzU@ldM2)|dgL_%CkWj81;C!u8DuFFt=h}9jWUo4I^3#X9B1{1!s{>hOuxW<#mQ*cx z`&oM$G2pb8oI?^Dy0+i`Vc!hIl_!7%+o8<3J{4Z$l^m=PGa=!;6&O~?-OOFsIO`5sPM+$*0kx&HCSp+vsVd%rWXk(9C*^WcZtc9 zN@Egu0_&!z@c6XFGhcv6vt{nfYJ|!gla?{DFq`a;v@JCoR67JLERD*iK9A+TSp#eb zKiY6xb8eY~GAse>ufSYoXZ4JR0t$<0Zl8AW%15Un9jpLGVq!%!^2y*t`_RzzAw4VL z@W69KPLqj5zf^edJ@${cpPU2ZGy%-(-3r*{5V5DpYP}x*Jy;4|cyHdd`t}xgK{Y0T z>|o04t#?E3Ujv_AUXnjCc;H5Cp|N8R6x{#@F+qrAZMeNsHwbi_KM?kC=_x+ z^~M$j;o0}Ph(e4^MD=A9TZ?m1x^GkGz>5y9u~{B1W#CbqwNhzF$-i+7VQECbg>ou2{f*syN+tIGt#>o0^G20!)PQ zgA6(OWLZk(8sPvhKoV+}PX;I2s$HI(yz!#?N}R3c*gEVb%X~~o$txm0e*k%8e#}c{ zd&tBQ_Rd(tC^yjNpd#qk78%4AvU9s1JIJRiQ4zBpd0Fy!!d$Y$4E}0nD+dGh0|qZ~ z%$6j%#n7WU2}wFQ<;}99e5%RU)n3HB>vqfQdAC-7Xu;cKM3Xu|TNUk3!cHX)ezbMV zU8?d2Tv|v~KY;@vh-?8@Ha435!8SjhZ~{l3&uMw6Fo~HB@GEvuw1-w{seXa@tr*5} zlMykH|18~`q^1SoSYd*5XsR?u8AgMm!aN=f;O=whjjC=*tmPN(OgmOc&{bi?%xJ`T6E4xv)49z&-Fyszd=qbZy^SF83CkV;03Ze$@${ z0{iY`f?Od4ydsACu?3B08g2W4fX>h_s0bcKp;IMF9oLwX5f6s)=S%4)D;0rI0_MRi zra;6(c{JQZjZ$E9j}4t+btZs2U%ab7kfZ5?m|B282UX$k16eO0a3S}LXxHu5~2CQ>|jjtlCVirl`AVlT+b6vvv6S zeJ+IcX#U8iRhRzQ7E^jX_sp{XyN|3r^+;2ATI_ETJM~N>m;)Y6?0oO%HvN6o-{2&) z{X~td$8V|=m)KHvN=>ZsR>N!TsFztcYBi(UbI5(RCt3<-1^nSJ@9@LK+tVc#VVzJ&^tAg_6VD!LeDRN zpi_oJbv@T!Iu5l{;qCRN&)QZt8X1%VK+q`>p=zViOXF1gEUKLk5<~(MK*IeF{}*bc z!u!cz(B;ZcmcBKy zXCMadbYH)c6SaGT^0)IMSAY|#6H*UNL)ZJcje=OIhNd#1=}6DWArY)k@-tm{oVyBd zTLDVcXv|wg7})d0YS(=FF&rEOaK&N{C**oEI-H9R0OiS7Lb7w!i+e_EsTsr|+)H@( zJ6b!(R6thCs;`~-aHTZd%Wz-za9=DvJ0y=u)#l*?e~0cC=rs>LmbhO~Cb#Z6^PAFk zHR7rclZExS5FLO~2Dl!se6NQOi}l>lM*LuJ770CIVi2HT+Q?knIQ8Ot)vG6 zJOZQ4mbcpMm^g=8R}uE@fGeT^7@byF*#TDvQjV^aA~0JJJqxK!x|gi36)T zoy?9Kpv2|K_ze!#lK>cMZ>*WGU1R>JSI^%}zo5c<<+F?4er+Z$TmjV08b#6NsSB?% zWzsCD95;ZRT2=(YLI{NB5CDz7fkp6JQ7s?ya@3_gLxuOvt>foT*bHr+0Inf`h&7CS z12t+xfGt$Z0bQg2=w)bv3a`hOmRT3B#kDAa`Uf{^*+Zt&^ChAv!XJLcwKPMYw3C%L zVZ^@KpVc0M+>`;}?qv~47}-SRzOIN@fNng&7}Ghs;JCn(Z%;`FC)!>Gua}(cTzZvd zW2frEYe0T%Gc%B&CIFDxi%(puF=H`fI67OPPOt|HbJqx|D1a+@;!cNIhXTDhLXsy} zQuph%HZ{4JJNQ4yr0MxYi>P>nz>BYBRCiIAu;p%V-s3V7?mDS1>)LRHt{ zjP;+mHv~}6>xM!pbndQnGHuPR2w5b6n$LZ)3LW;p-6?+aJL!1TZuX?2fG2^cHYs3dZ|a0EyFJw=7$+z^6d~2@q%RSw4qP#R7Z#Q`Gexv<_MU(@u9?duFnG zrpZPufCSJYmPBd3#5n}SrXg{Eghw}7@9I#_L6=A}nd}p5DHbjQMy&TGpXuu7G= z#i)|Jp>TJzxx02rUA<-y3A+OkEEO(mj+xdRO?ToJc5~MgM;C5%-!G$-^2Kd;344~7 zBIn{4C0(~qFO9i}qw(Apz-4sY``ihz{6(F%tM@z%@wWiViRR;;Ml1tSflv#a?yqN__j}%y)hT_B7h>(mjN%cEAR?v0SjQN^jpFGx^dQ$_`@+i@-gZvv zUw#}e5k1F}m@6i9XE(6cwGBB>%z)=B9Y`Zuf+Y7~bl_JkRN?aBd-jp3{mVY>VFp`J`K$IDAy?+cxOXlz_>#{WD9CRhF z2-3lcw5XoIh{5C3s(~I-;q^~>u*>9nNMW{PLfd71vWIEcKCOG6Op`N(@WUY>m<<6l z^Y9ZB|5LBt&oTE#?)voIi)OtE8&d&XW|fsn;JF3BVvr{-r|Y%PUrMS_(-;kPZlDgDiRbD&{WfqQ%4S${Pr-SO8hlc_3$>3a{hF4~8H4 z3~?s}h)i%<1^ne0U-OsuLK5gx;dOth;L9FE2gMB_nubu2&7nV?_oszJ>%=_)+=z_4 zKUTP%hpvd{cZr*z5s}fOT9q5GDXF4+_02n+*9kknw@JQ zAHwIIZv6bueIZw4_KoBI5clP++t#wE{Z+u3d+*nOD4yImj%6N=58Aj;^uqQccaE+| z7s@IS!omV95WnFtIL1Q2wat2`_+V2%f3AOWKD;HuEl{4x2d>6v4Td}ZB)ba(?Qj8f46 zB`O2`#NmaqTnTM;d;>}?LZQMW-7x@00h-5-#WJhfjBf92X6`o$m%k}Nn^DdvRCZ1_ z+&_%7lO2U4)=Dx8N9Cb@{U|7$y}B}S6fCp~1qY9=g}V#%S|Bn>a9z6#C(6}WD2S3_)5|1@% z97&lcz>@|)AT$WPH*KAc9K6I;7C1Q3c3l<9m~@2z{5f#$OV83eFx#Gv`BNPlI02Hh zC!Y+?FSS=Nu^<(mF`QeMdbAl)>`lt(0T>VXuI366fVX5{G9e1j3$WlwC$kY0HMPp=Fj0 zrL3|_i`lzomC;hl{M|{G^lTaT_kACK^i{;C)9G|Nold9It5q3Z7HJr(+lE>bi)#r< zHr)}gMO86J*=!EGL$@TET-DO0OXtjNPP7g-C)i?=$Vt^oL(9ta<`fxqSb=0GY73=h87ac_9Sa`SOBT_p32qSm0c~d(IB%~OVk`@b6B9EQlESY z;de$oGpX$#cE6U~GKa!ibZy2;In1in;il9aZ;Kx2(V|qDw-|$!Gg+bsC2Kl%F&q3_ z&j}y(i&7I(N9N0^sC&PioX2@3w`gz`)(JM7t%(k+4rChprWU@?HhAzGlR778(bNWi zq*GYee#1(+32El!M5K`MHNX{ujpoNjGi)!`cBnmrqCV&_cv0%fj0z`oIM&akTjQ-3 zI}l1}q=b&!e)-sNJwQ>f7btb)@3bH#EsTzZYc19Rc3M*jJB@UnJ$5Ci$(x5Jb?fx9 z<)?3ZV$$;fFmwgUptKar+AepWJO7x}%u9>K^;h1RI4#Q%28s3sb@hlvr;lqBI#&PJe<49!il*RwM32Qwv}O}pzFC1 z#@O|*vxdy+cRNH;?al4$^i4$-d2pR?>tEX3<%LOYoObI_>e#n@aP_>{R6g3Q5QYXr z9_$}5@uf*UerM;hJG+YGY}reBBAX;7gA{L>eV7a)kG#F$2+tn-4<=N+qFbBlFj)lNkd%ep>bzWL#jhe(6fq2 z5`I)Z0F!q9a&k)I3lmV$^}|sSZG$PD>Hs2Wo0X6a0WMT*dR1(HO@(`7HUwWasjt7R zYp&Ba15EFi-9KFWx-}*_+M(GJy(l1!QwnM{4}J8w#z~XPblR#6&h#T(KwP?)Wg12$ zYw?cCT4M18D=Z94K>3g4J%;tqHaiYYYfFj)*G=u~4EOnbem#_e5pQH-zg-tq80c zpq%4i+Q(bTdJwPK9ZXuum^K<3cW}`7OD6Ts$#&Hi4opR!mi4zdfj$Gpn`ntQX$h!V zGFwxv(OjF-HG^O-n`3Xjq-!u*j4U>{Tz_Kca+p)BdX&v>HABDb78Uh>Y4|~JhJ6Y; zJEiv4FVic$z}4A&T;j43E#_9`zz*a5>tDdy^3P#Enp9SRMZg3&wMAGnr#ae|kfZ@S zqJ}uE(Y6??M`b?1tiURNk9`foWlpN|B<|)X-@Q0zQdxo3NtY4o1QW$emthx~((SN= z>A$WT72fi16E86CXjYhkvVI@fVSAC}pJB~s1!M^6)@kfkK5T%l__5eISmG=w${w9# zRXGmc=0s@InK9ZD9Yb%7{ zq{SqQ4(m8bv#9dR>!uf$_4w1I-m5+FyQYg@g4*s3lz4O`I{2mz2cpNvq3&I=eJ-U8qq37o_E9!_0vkNspUmq4W5MEENOE4TKnw>fl z=z;UyqRssqwFWwS-3fAxF53ujhI`|bU^q^k=X!IBlMDzkeYv?6G^hRc{X2&VdAx`d zG@qxLBPj@?m?Rv$&3o8pTkn2QX}f4rH}`GPy;OfNBw(BXGM%;n+YF&?N`|&njW<&t zs`+UqZG7P08H)Z6!y4tyE{>DIaGb*tCpFbv_&+@sK5G7u-?FuM0CxM-U7b$NipR<1 z#)&__2ZrMujX2$=nt-Q^7EI99VTq$xWq#uerv$@sBFpq>?3YpecEmeQb{te#QHQ7O zO+GX=2}H$=Fh;#D!El^7^m>1aQ-})H#$cI8Y<^xjtnwJZS=@k=h=K&eaiZ0%m+;2f zr&PIZZ|3Dt)E{rmG6h8fPT3F(w?!u>fL60dw$vQ0lA|J%hN!X0iNvyqOtB`W5EIO< zIjohFEYSmD<+DfHqqU?Vk!G~b%*hTd9xM=THBMAw=NmN=6<**zqd>1gI^_a--Ky`I zW}dw3c}7L8>KePZOrKP4RSWc~opk$PPU!;) zPHjKJ;76U8xb>&@Ls~29IW;QJ(ITA`S>qmERgZ-I*v%TbH&$Nx zrzy<6whq9G`OGPncrC`_u$mnj7%L7-LXsPWShiM_e>!q;R#=jlWyt9sjC6-IM9T|J z4-*J}Gx!s=bGD9+-!k}{P(`Kn3vrrV{Yf=*k_C*v0X7X+azE8rYx+<`odzF|W*!#n z*014-W{O&N!IX9THn!)@Xv1K1ojJ+M?{*m2lJ#gbibUVOE;AzS)7)hgb>_DC0(mZ1 zRov^1i|pk_LSt&?%J<)H%+W|uKN?sqZrG0ID044!F*|aomP8F;ldCb(=ia@Nxk~0? z7rXq#jt4G2it|u%&d5x~72Ihoz9PA=4_uJt$7cm#&Y_RNgY6Ow$2}Eshft<~I09-{ZupZv9X{%) zuLfOj+zO`uxaou6)QgBEyc~@`kHL?mQK+T6+oVo95&ybM&eDoo_+4`W94tJ-dd-d| zK1_fiNhlk_&=@uG%|AH{f-_*v+TYA69XqhvS`*xRgpkTeB90r`_>g7%=b!Eb)=cj- zsj=<$6;#hQVG?CPOvmQAN$v1SXur!@Iuk91vss^spIPvMK^25#KmYFI*s$D|R_Zgx_<^g@!0&Rw>M^F&keh+>;rgB>t@iqt(;17n4 zxIQ28ETA|A4Qz*_wm3es{_@!+#I%Wo{yFpWS!0g1>E;&D^A_L*vU+wzLc$t#t%3Fg z11wQ?=n(E(>E`w2!kvAqwudcR{=Bos^v#eHI@AFPG>$XX=Ae@MrAzL&J0deWiI7iN+9>iqSW8jAkEMYWchxFy}Y z8Y5ZMkPBASMb&ma{IXItMQ`Oz`6E@Gxb7uPPzoEyhgUsnDdD?X|EN21 zTf`!8POR$HJt9+FdS5st7>*OS>^l0TJ^D^H)?1T0A+>bBMJJp4!YRRUoVZ`sJ9^`! zq6Av=PN71_0#geMQF;Y~3=U`9`b##xRExJV0PHbm)@a>|W&_ADEZFP$A(LS z;n>mhqARoyL>&;VS~`1pUhM|Bzl$~KSGP}9G~~hB?s7f##Cwe&a*4})vW?ypt zo~5XwPECDXzESdk4A!)EQ#&aB&(vnD|Be= z;0)b;NuUJ75#!RR&-Ugtw;+S_>|6S!($p7D35MfDJ4qi$af$_k+f{URtjiZgtsTHS zL*0uaaZbW%3Q?6X41N&s1`&b2oi->m@~)XhD}K2J_WTw1Y|N7PSv@h01|vM4i2!~- z@EpH@H>y4ETyOSW;bwipY@$Q+5Pw^WABygyG6|mJ7w|^>Y~fL#4%%9~W;ozy8)ku1 zg5fwXAq^9~c|r}0b0)W?agIo&FgHszbi0eop=UKQrd)AbZ1HlqU4T;50Bf|vjy9*@ znS_tGFIH9RA0`8-S_}1U^U)BnJovsyUr6~R##w^lI8pu4aWnFX9-KTisdqa^c6;6z zEDvVQ2tG+L949(PnFB;vaG%|>RsU;Mn4)HT{3zF*i30)WPD-aA7>+XnEuP?pb}Qdy ziQb)h+2;JbKCn15Bav?}YGSR-s7c~|NWWB%w}$of!*rxYJ0;3Cd_5x<*duhK`?27P z9~h1kZ8rT&Z=7`9ylzsLF0?DacMtAn*i0lwTd8$~ zL?ys4k=9|e;oiNMzcg8;%0 zqU=%|QXSp~*3qwex50g1E$1HJxa-?&*v5xE8g-x5dLK**PD%ve07iHY;D6(oG^c2~ zBiRCWRtjnavioaGdoL6{KbK_%AB!gYi67!3flWLK{7;Ust%=DAK#`Um?QDp$fRmUw z60{;_@47x6y5x!Zg*m^SC|%w{?UyG;$1Qi^A{%}M4zS&;pZ2heeLcJAx4ri`hs`ly z3!qon4!w*XXgsYa?mjrJkGCX5#aPhGB}9UrT`e`KtbmK&L!jK%3&|&=XjCi=jKibM zBfS2Y!y8S2Qc9)UhfQDt(5-AbSeT*!km(f^4^@IembCI@$$v8H=D}9WK;0S(t2~ZF z86-?{CQjVZ>%_REJ0_JC()8<;Fu@%56NmX9#WC8JGz2MX3Jo~}@Lyqp2KRZz)V&q! z>0J~xut54e(~4V(e(2}PhyO-2=(z#7!mSYP;jl)U2PIo#;9#9B-*HGw5c^A5fIoV$ z`@R2ndSFt*IYzWix6&9Bi>_M;U*;e!nIXahj-_GG=74oT8w{$HeAIQVx0ccOf#t^v zI8%!@G@D6%Gdn#on~P&tG5El4a%}{ZFFjBO_(O@({1&RX{y|a#wZug9sAx}YKm>l& z3U9(*{yt=w!V5TGK0@bB2Kc;m*9h=egSDZNAcf4j)eeC+)Mpy#S_$pySpbzuz@qUDmIl9oBHvvp1x=%JT7t9c+H@v`$o7YoC3~DD2?93YARJYG5um`CF%Aqi z3^dfRpX8%1t6A;orm}Sub$p#RD=UoXi{8e(_$!7~I0M}kTn&&r1>sNb*t%kh+O$bn z@9p_|V(?4khnfX=n8ziT(Vi6o{hS%motKDFD`I2A*T-M;)ZBk@EQT)r*K1W2%)dBW zywwie(uwywo7rwv@leuije+w<@PFe;hh74iK;tYRUhUWH-Gd@tR7b8AL$2M2k6s); z0G<`WOCdOS_*0uL_y0mDE^3-XBZRu5p$TU;MklB<&4z@|z?REn%M*`3epxPeNk#o< z#rmdYg4-jZ_uyA;A#URGI;`l%u*93~$>1zavcU-}34n1`Mhq1Z!;W#ke>dVQ*q@c( zls510%O7(LcOZDdS;6dxv+4s;kAB37UjQaFTMcw{+#g8wq zyv6xZ2<#jN{tr;E;YZE?>EZjCYruNH`B>!mkME4;;I#qVSsWuqya&SWuv%hp{?=OqxA99zywmZd~B#Y9-O(xsK$fbffmB>(1+$!|xZZ1(S2svJPJy zKU<3oi2^^n@IcKOU`s^BWmu@B3edoZQ{Tjhad#9?JyDrzxN0~vYj(Dk1_y+868=U$ zYCx83LB6m?9gOM9|TZAp620)LLddLg7<3_U{iR}C4(tLd$wF0AQyac;JSvO0YA z`YxR;O%(OD{q&3{<-3t);^Al46J%5_v&ByBCKk4UQyaaKdPl6@@%O*VJ=tDUQI**} z*RI&uN(syG()_=RhcHD!_r?=r6#hR13^rVaCL?7bpavagHv?#-!ch3T+5=C_a#m5) zZC%?q8hqV^oVDS?O%Al3dCe9F2Hrc&)njlk1iN)`x1jNM4`9eg9ayJ%ofh8|Qh33` z&T)na3^1+zR72LI%<0YW6AmBPQwbTZ+-Js_oYvBcI{I0o5l5$jj~?L38uS}Z>_EFM z9$XwSd$BfIavCj~TWs6WQi@vCr8cknwh3wi;jSswkg%;vbbK<*f4z!1PP6J3Jvwek zmBd6i0<>s}=%^8E4UHK*%MQ+;5M_V_YmHFDhl0hd4hH5}Js4$hY0((a@0jZSjs~+K zxoG7&gIIlNH33BiKkkS_LnX#0tqHWM_k)_iCIR1Qb;RtSuY9yfIW7y7&6(9(7uQ z_`K}(zfV@G%h5vhG_BL25J5zD5?&OE(Grjtv1z7;&MA{xB+IFsU4Q>o2;T_8GC1d? zUGNk>SP((F;KNriUhtTudK|JWfPlUSp0ju_vY3vk0Y2l7FWQYB6sV}Ikfy8NIN47e zhU3Ixpfm&;aOPhy%3P{-V?`~n;9~x{pZ5eeM}Kde5)8-L2@T62Q2b!+S?~m32rT6$ zOp@KJvxT0$2JsV2m(0RS`QK-ZdJG9Yy@a!+I)ne*6FDU#su!Y8WgJ1C)I1ecV3 zIc!eOUqyfg13#42FF1|Yq45|*+f2h{AGA$;JX!cX2VB?y^MVUbYx)qPC|JQ`z(L!% z>qDRXRN~y6g`FCToeDI-Un&wbGC4CWXssH36}j%hcrii(rv$@sUdEo}HoGmD_jQEE zDWzB30ZS0Z0Gtz>;>MKGqCE+ow^t(=+6%!u9adA@%| z9lB!jMlHc`)M$(8gcKutHa9<$_-FNwirPM)|I@z)j`qbK2?lV!?DL`Y3^AqfHlnZ_ zU1dQZz}XUpwvf;`5m!Vd83MmrtZTgB{sp*E2do|oTyWvK36U6B2RGIrG=6F!SI~xV z!&>9OutSNnz@qvXkH{;#}~M9<3d; zNj_=`(}tUS8dp=)7dNInPb~o^d6=6xCnCyiPNCimV|}Azqt3wk?V3BlKX$!Xx-nxj z)m7ZpwMb|Jj>V+L1BicvIyq=qY!*fBo2BZRU)21A~; zQBfb3sgeIkPio@`QO}3)M&cxR#wFsY(FuH@!An#8jLn?sWNSr5)sU-Nvf#X+L4x5p zvB&xhLbIWdF9zpKeDG zZa$#zxc$&RgO7S=^?^b+&(%}hae;lvcoA3KLHHRizD1Ecy7W{v#+u}agZNW4`$6(> z!|U1t7}eivW!}`6SVhAblZxpaW>pYYpI|?z?#6%hgjOV84;+NW*y@`tupBi4Idz^{)`V z_;2kB%&*!^YrxRKRGJDjmO zh^fZ6M&M2i)W5`f_)+`Z9hdw`Y4B{)AYyuNi!k^Tte25C2u=B^!ke0fM!tJa5Yos# z@;Aq`W_`xN7}mVFqw%93qd+c}LDO!wQt~AjjuXkymm|E9t-mGao&58Qk0Fe__~qSi zY!Cv%G@YQq$ky#M7KhFK9+v*Vyw;1^Hd(l09Exns#l*;P>|kW>kkp??-wrAuTI1Ax zflSKju5R3Nx~_w?XgvVC8ggyl>R&d&ykG@RE7sP(CA`p5piD$oy zao>f6%fB@8`3c?!N=Vm7=uVgTK{e{|Dk?ckf+}#?0USpqu*OcLtkJAX z-+sLrLS&!!E0KTAgp$+~;IUv?UI;>NX%wSaT0z?~h;c8hqg_o{%wNA6m~D;ewStIRS{fJziN5r7r|1NU!Yv}hI?rfosF!H>G;^4==vzHO@TLfR^~vDqA} zWh2HWzZedK>!&Ig(L+Fxj#sO|;uP>z=LerkSH8cZ9voLG_G;2lZr5r$Zb3589$m&B z9mO8)8Cte^@;JCsvv%C<3x_WB;_S|VJjh^490Du@&buQWkN)`RqcVYT>P>ZG1`Zxe z!Q#AJbhUPAj5*Pzmbxi-qgatl-fJeLEcmKk&T0Kd}!4-sn4|mR=r3U0t

}zh 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 diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 84f3bb03..059b3bd6 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -477,11 +477,11 @@ def unique_distance(self, target: _EquivalentBorehole, dis_tol: float = 0.01) -> if j1 > j0: # Add the average of the distances within tolerance to the # list of unique distances and store the number of distances - dis.append(np.mean(all_dis[j0:j1])[0]) + dis.append(np.mean(all_dis[j0:j1])) w_dis.append(j1 - j0) else: # All remaining distances are within tolerance - dis.append(np.mean(all_dis[j0:])[0]) + dis.append(np.mean(all_dis[j0:])) w_dis.append(n_dis - j0) j0 = j1 diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index afbb54e7..3cb1eae9 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -8,6 +8,8 @@ from scipy.cluster.hierarchy import cut_tree, dendrogram, linkage from scipy.constants import pi from scipy.interpolate import interp1d as interp1d +from scipy.sparse import csr_matrix +from scipy.sparse.linalg import spsolve as sp_solve from .boreholes import Borehole, _EquivalentBorehole, find_duplicates from .heat_transfer import finite_line_source, finite_line_source_vectorized, \ @@ -1582,8 +1584,10 @@ def solve(self, time, alpha): A = np.block([[h_dt, -np.ones((self.nSources, 1), dtype=self.dtype)], [H_b, 0.]]) + # A = csr_matrix(A) B = np.hstack((-T_b0, H_tot)) # Solve the system of equations + # X = sp_solve(A, B) X = np.linalg.solve(A, B) # Store calculated heat extraction rates Q_b[:,p] = X[0:self.nSources] diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 9efea0f4..bf38026f 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -43,24 +43,31 @@ def erf_int(x: np.ndarray): abs_x = np.abs(x) y_new = abs_x-sqrt_pi - abs_2 = abs_x[abs_x < 4.5] + idx = abs_x < 4 + abs_2 = abs_x[idx] + y_new[idx] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi + return y_new - y_new[abs_x < 4.5] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi#(0.000394626933*abs_2+0.997394389926)*abs_2-0.559959571899 - #abs_2 = x[abs_x < 2.5] - #y_new[abs_x < 2.5] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) / np.sqrt(np.pi) #(((((-0.008064236467690 * abs_2 + 0.062357190155) * abs_2 - - # 0.158413379481) * - # abs_2 + 0.033117020491) * abs_2 + 0.556519451607) * - # abs_2 +0.000535001984) * abs_2 - #y_new[abs_x < 4] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) / np.sqrt(np.pi) #(((-0.123_560*abs_2+0.603_273)*abs_2+0.003_809)*abs_2) +def erf_int_old(x: np.ndarray): + """ + Integral of the error function. - #y = x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) - # assert np.allclose(y, y_new, rtol=0.000_001) - return y_new + Parameters + ---------- + x : float or array + Argument. + + Returns + ------- + float or array + Integral of the error function. + + """ + return x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) def erf_approx(abs_x: np.ndarray, exp_x2: np.ndarray) -> np.ndarray: - return abs_x * erf(abs_x) t = 1/(1+0.3275911*abs_x) return abs_x*(1-(((((1.061405429*t-1.453152027)*t+1.42141374)*t-0.284496736)*t+0.254829592)*t)*exp_x2) diff --git a/tests/test_erf_int.py b/tests/test_erf_int.py index 23917b63..59becfc9 100644 --- a/tests/test_erf_int.py +++ b/tests/test_erf_int.py @@ -1,4 +1,4 @@ -from pygfunction.utilities import erf_int +from pygfunction.utilities import erf_int, erf_int_old import numpy as np from scipy.special import erf from time import perf_counter_ns @@ -6,16 +6,16 @@ def test_erf_int_error(): print(f'test of erf error and performance') - x = np.arange(-4.5, 4.5, 0.01) + x = np.arange(-3.9, 3.9, 0.01) tic = perf_counter_ns() y_new = erf_int(x) toc = perf_counter_ns() dt_new1 = toc-tic tic = perf_counter_ns() - y = x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) + y = erf_int_old(x) toc = perf_counter_ns() dt_old1 = toc - tic - assert np.allclose(y, y_new, rtol=0.000_001) + assert np.allclose(y, y_new, rtol=0.000_000_01) print(f'new time {dt_new1 / 1_000_000} ms; old time { dt_old1 / 1_000_000} ms') @@ -25,7 +25,7 @@ def test_erf_int_error(): toc = perf_counter_ns() dt_new2 = toc - tic tic = perf_counter_ns() - y = x * erf(x) - (1.0 - np.exp(-x * x)) / np.sqrt(np.pi) + y = erf_int_old(x) toc = perf_counter_ns() dt_old2 = toc - tic assert np.allclose(y, y_new, rtol=0.000_000_01) @@ -38,9 +38,9 @@ def test_erf_int_error(): toc = perf_counter_ns() dt_new3 = toc - tic tic = perf_counter_ns() - y = x * erf(x) - (1.0 - np.exp(-x * x)) / np.sqrt(np.pi) + y = erf_int_old(x) toc = perf_counter_ns() dt_old3 = toc - tic - assert np.allclose(y, y_new, rtol=0.000_001) + assert np.allclose(y, y_new, rtol=0.000_000_01) print(f'new time {dt_new3/1_000_000} ms; old time {dt_old3/1_000_000} ms') From 096dacee4e73e84b35bd8501483ae946c78a6202 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:41:58 +0100 Subject: [PATCH 05/11] reset --- .all-contributorsrc | 13 +- .github/workflows/test.yml | 2 +- CHANGELOG.md | 4 + README.md | 18 +- doc/requirements.txt | 2 +- examples/comparison_gfunction_solvers.py | 46 +- examples/profil.prof | Bin 86245 -> 0 bytes examples/regular_bore_field.py | 4 +- pygfunction/boreholes.py | 552 +++++++++++++---------- pygfunction/gfunction.py | 299 ++++++------ pygfunction/heat_transfer.py | 22 +- pygfunction/media.py | 65 +-- pygfunction/networks.py | 4 +- pygfunction/pipes.py | 12 +- pygfunction/utilities.py | 38 +- pyproject.toml | 2 +- requirements.txt | 2 +- setup.cfg | 2 +- tests/boreholes_test.py | 28 +- tests/gfunction_test.py | 257 ++++++----- tests/heat_transfer_test.py | 14 +- tests/media_test.py | 213 +++++++++ tox.ini | 5 +- 23 files changed, 942 insertions(+), 662 deletions(-) delete mode 100644 examples/profil.prof create mode 100644 tests/media_test.py diff --git a/.all-contributorsrc b/.all-contributorsrc index 653d45c5..e2eb0793 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -31,6 +31,16 @@ "ideas", "doc" ] + }, + { + "login": "mitchute", + "name": "Matt Mitchell", + "avatar_url": "https://avatars.githubusercontent.com/u/2985979?v=4", + "profile": "https://github.com/mitchute", + "contributions": [ + "code", + "ideas" + ] } ], "contributorsPerLine": 7, @@ -45,5 +55,6 @@ "description": "Founder", "link": "http://www.ibpsa.org/proceedings/eSimPapers/2018/2-3-A-4.pdf" } - } + }, + "commitConvention": "angular" } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 577fe969..29bb9918 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.7', '3.8'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] fail-fast: false steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f3ac7b..1c639501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current version +### Enhancements + +* [Issue 204](https://github.com/MassimoCimmino/pygfunction/issues/204) - Added support for Python 3.9 and 3.10. [CoolProp](https://www.coolprop.org/) is removed from the dependencies and replace with [SecondaryCoolantProps](https://github.com/mitchute/SecondaryCoolantProps). + ## Version 2.2.1 (2022-08-12) ### Bug fixes diff --git a/README.md b/README.md index 353c1cde..3f72ad74 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,8 @@ total): ```python import pygfunction as gt import numpy as np - -time = np.array([(i + 1) * 3600. for i in range(24)]) # Calculate hourly for one day -boreField = gt.boreholes.rectangle_field(n_1=10, n_2=10, b_1=7.5, b_2=7.5, h=150., d=4., r_b=0.075) +time = np.array([(i+1)*3600. for i in range(24)]) # Calculate hourly for one day +boreField = gt.boreholes.rectangle_field(N_1=10, N_2=10, B_1=7.5, B_2=7.5, H=150., D=4., r_b=0.075) gFunc = gt.gfunction.gFunction(boreField, alpha=1.0e-6, time=time) gFunc.visualize_g_function() ``` @@ -109,17 +108,20 @@ To contribute code to *pygfunction*, follow the ## Contributors -[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) - - - - + + + + + + +

Massimo Cimmino

💻 📖 💡 :rocket: 🤔 🚧 👀

Jack Cook

💻 💡 🤔 📖
Massimo Cimmino
Massimo Cimmino

💻 📖 💡 :rocket: 🤔 🚧 👀
Jack Cook
Jack Cook

💻 💡 🤔 📖
Matt Mitchell
Matt Mitchell

💻 🤔
diff --git a/doc/requirements.txt b/doc/requirements.txt index 929c3ad2..41f98c39 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,4 +4,4 @@ matplotlib >= 3.5.1 numpydoc >= 1.2.0 recommonmark >= 0.6.0 sphinx >= 4.4.0 -CoolProp >= 6.4.1 +secondarycoolantprops >= 1.1.0 diff --git a/examples/comparison_gfunction_solvers.py b/examples/comparison_gfunction_solvers.py index 9262c256..f31528e7 100644 --- a/examples/comparison_gfunction_solvers.py +++ b/examples/comparison_gfunction_solvers.py @@ -21,8 +21,6 @@ import matplotlib.pyplot as plt import numpy as np from time import perf_counter -from cProfile import Profile -from pstats import SortKey, Stats import pygfunction as gt @@ -43,7 +41,7 @@ def main(): # g-Function calculation options options = {'nSegments': 8, - 'disp': False} + 'disp': True} # Geometrically expanding time vector. dt = 100*3600. # Time step @@ -65,27 +63,21 @@ def main(): # ------------------------------------------------------------------------- # Evaluate g-functions # ------------------------------------------------------------------------- - with Profile() as profile: - t0 = perf_counter() - gfunc_detailed = gt.gfunction.gFunction( - field, alpha, time=time, options=options, method=gt.gfunction.Method.detailed) - t1 = perf_counter() - t_detailed = t1 - t0 - gfunc_similarities = gt.gfunction.gFunction( - field, alpha, time=time, options=options, method=gt.gfunction.Method.similarities) - t2 = perf_counter() - t_similarities = t2 - t1 - gfunc_equivalent = gt.gfunction.gFunction( - field, alpha, time=time, options=options, method=gt.gfunction.Method.equivalent) - t3 = perf_counter() - t_equivalent = t3 - t2 - # save stats - stats = Stats(profile) - # sort stats by time - stats.sort_stats(SortKey.TIME) - # save stats to file - stats.dump_stats(filename="profil.prof") -# ------------------------------------------------------------------------- + t0 = perf_counter() + gfunc_detailed = gt.gfunction.gFunction( + field, alpha, time=time, options=options, method='detailed') + t1 = perf_counter() + t_detailed = t1 - t0 + gfunc_similarities = gt.gfunction.gFunction( + field, alpha, time=time, options=options, method='similarities') + t2 = perf_counter() + t_similarities = t2 - t1 + gfunc_equivalent = gt.gfunction.gFunction( + field, alpha, time=time, options=options, method='equivalent') + t3 = perf_counter() + t_equivalent = t3 - t2 + + # ------------------------------------------------------------------------- # Plot results # ------------------------------------------------------------------------- # Draw g-functions @@ -97,7 +89,6 @@ def main(): f'equivalent (t = {t_equivalent:.3f} sec)']) ax.set_title(f'Field of {N_1} by {N_2} boreholes') plt.tight_layout() - # plt.show() # Draw absolute error # Configure figure and axes @@ -151,11 +142,11 @@ def main(): # Evaluate g-functions # ------------------------------------------------------------------------- gfunc_similarities = gt.gfunction.gFunction( - field, alpha, time=time, options=options, method=gt.gfunction.Method.similarities) + field, alpha, time=time, options=options, method='similarities') t2 = perf_counter() t_similarities = t2 - t1 gfunc_equivalent = gt.gfunction.gFunction( - field, alpha, time=time, options=options, method=gt.gfunction.Method.equivalent) + field, alpha, time=time, options=options, method='equivalent') t3 = perf_counter() t_equivalent = t3 - t2 @@ -203,7 +194,6 @@ def main(): f"(Field of {N_1} by {N_2} boreholes)") # Adjust to plot window fig.tight_layout() - # plt.show() return diff --git a/examples/profil.prof b/examples/profil.prof deleted file mode 100644 index 5849895121869cbc602c456ecaa5bac0f6c90d2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86245 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 1.0e-6 + self._is_tilted = np.abs(self.tilt) > 1.0e-6 def __repr__(self): - s = (f'Borehole(H={self.h}, D={self.d}, r_b={self.r_b}, x={self.x},' + s = (f'Borehole(H={self.H}, D={self.D}, r_b={self.r_b}, x={self.x},' f' y={self.y}, tilt={self.tilt},' f' orientation={self.orientation})') return s - def distance(self, target: Borehole) -> float: + def distance(self, target): """ Evaluate the distance between the current borehole and a target borehole. @@ -75,15 +70,17 @@ def distance(self, target: Borehole) -> float: Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1.distance(b2) 5.0 """ - return max(self.r_b, np.sqrt((self.x - target.x) ** 2 + (self.y - target.y) ** 2)) + dis = max(self.r_b, + np.sqrt((self.x - target.x)**2 + (self.y - target.y)**2)) + return dis - def is_tilted(self) -> bool: + def is_tilted(self): """ Returns true if the borehole is inclined. @@ -95,7 +92,7 @@ def is_tilted(self) -> bool: """ return self._is_tilted - def is_vertical(self) -> bool: + def is_vertical(self): """ Returns true if the borehole is vertical. @@ -107,7 +104,7 @@ def is_vertical(self) -> bool: """ return not self._is_tilted - def position(self) -> Tuple[float, float]: + def position(self): """ Returns the position of the borehole. @@ -118,20 +115,21 @@ def position(self) -> Tuple[float, float]: Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1.position() (5.0, 0.0) """ - return self.x, self.y + pos = (self.x, self.y) + return pos - def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> List[Borehole]: + def segments(self, nSegments, segment_ratios=None): """ Split a borehole into segments. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -147,37 +145,37 @@ def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64] Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1.segments(5) """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] - bore_segments = [] + segment_ratios = np.full(nSegments, 1. / nSegments) + z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] + boreSegments = [] for z_i, ratios in zip(z, segment_ratios): # Divide borehole into segments of equal length - H = ratios * self.h + H = ratios * self.H # Buried depth of the i-th segment - D = self.d + z_i * np.cos(self.tilt) + D = self.D + z_i * np.cos(self.tilt) # x-position x = self.x + z_i * np.sin(self.tilt) * np.cos(self.orientation) # y-position y = self.y + z_i * np.sin(self.tilt) * np.sin(self.orientation) # Add to list of segments - bore_segments.append( + boreSegments.append( Borehole(H, D, self.r_b, x, y, tilt=self.tilt, orientation=self.orientation)) - return bore_segments + return boreSegments - def _segment_edges(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> NDArray[np.float64]: + def _segment_edges(self, nSegments, segment_ratios=None): """ Linear coordinates of the segment edges. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -195,22 +193,22 @@ def _segment_edges(self, n_segments: int, segment_ratios: Optional[NDArray[np.fl Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1._segment_edges(5) """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.h + segment_ratios = np.full(nSegments, 1. / nSegments) + z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.H return z - def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> NDArray[np.float64]: + def _segment_midpoints(self, nSegments, segment_ratios=None): """ Linear coordinates of the segment midpoints. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -228,18 +226,18 @@ def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[NDArray[n Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1._segment_midpoints(5) """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] \ - + segment_ratios * self.h / 2 + segment_ratios = np.full(nSegments, 1. / nSegments) + z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] \ + + segment_ratios * self.H / 2 return z -class _EquivalentBorehole: +class _EquivalentBorehole(object): """ Contains information regarding the dimensions and position of an equivalent borehole. @@ -283,19 +281,18 @@ class _EquivalentBorehole: Simulation, 14 (4), 446-460. """ - - def __init__(self, boreholes: Union[List[Borehole], List[_EquivalentBorehole], tuple]): + def __init__(self, boreholes): if isinstance(boreholes[0], Borehole): - self.h: float = boreholes[0].h - self.d: float = boreholes[0].d - self.r_b: float = boreholes[0].r_b - self.x: NDArray[np.float64] = np.array([b.x for b in boreholes]) - self.y: NDArray[np.float64] = np.array([b.y for b in boreholes]) - self.tilt: float = boreholes[0].tilt + self.H = boreholes[0].H + self.D = boreholes[0].D + self.r_b = boreholes[0].r_b + self.x = np.array([b.x for b in boreholes]) + self.y = np.array([b.y for b in boreholes]) + self.tilt = boreholes[0].tilt self.orientation = np.array([b.orientation for b in boreholes]) elif isinstance(boreholes[0], _EquivalentBorehole): - self.h = boreholes[0].h - self.d = boreholes[0].d + self.H = boreholes[0].H + self.D = boreholes[0].D self.r_b = boreholes[0].r_b self.x = np.concatenate([b.x for b in boreholes]) self.y = np.concatenate([b.y for b in boreholes]) @@ -303,17 +300,17 @@ def __init__(self, boreholes: Union[List[Borehole], List[_EquivalentBorehole], t self.orientation = np.concatenate( [b.orientation for b in boreholes]) elif type(boreholes) is tuple: - self.h, self.d, self.r_b, self.x, self.y = boreholes[:5] + self.H, self.D, self.r_b, self.x, self.y = boreholes[:5] self.x = np.atleast_1d(self.x) self.y = np.atleast_1d(self.y) - if len(boreholes) == 7: + if len(boreholes)==7: self.tilt, self.orientation = boreholes[5:] - self.nBoreholes: int = len(self.x) + self.nBoreholes = len(self.x) # Check if borehole is inclined - self._is_tilted: bool = np.abs(self.tilt) > 1.0e-6 + self._is_tilted = np.abs(self.tilt) > 1.0e-6 - def distance(self, target: _EquivalentBorehole) -> NDArray[np.float64]: + def distance(self, target): """ Evaluate the distance between the current borehole and a target borehole. @@ -341,9 +338,13 @@ def distance(self, target: _EquivalentBorehole) -> NDArray[np.float64]: array([[ 5., 7.07106781, 11.18033989]]) """ - return np.maximum(np.sqrt(np.add.outer(target.x, -self.x) ** 2 + np.add.outer(target.y, -self.y) ** 2), self.r_b) + dis = np.maximum( + np.sqrt( + np.add.outer(target.x, -self.x)**2 + np.add.outer(target.y, -self.y)**2), + self.r_b) + return dis - def is_tilted(self) -> bool: + def is_tilted(self): """ Returns true if the borehole is inclined. @@ -355,7 +356,7 @@ def is_tilted(self) -> bool: """ return self._is_tilted - def is_vertical(self) -> bool: + def is_vertical(self): """ Returns true if the borehole is vertical. @@ -367,7 +368,7 @@ def is_vertical(self) -> bool: """ return not self._is_tilted - def position(self) -> Tuple[NDArray[np.float64], NDArray[np.float64]]: + def position(self): """ Returns the position of the boreholes represented by the equivalent borehole. @@ -384,15 +385,15 @@ def position(self) -> Tuple[NDArray[np.float64], NDArray[np.float64]]: (array([ 0., 5., 10.]), array([0., 0., 0.])) """ - return self.x, self.y + return (self.x, self.y) - def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64]] = None) -> List[_EquivalentBorehole]: + def segments(self, nSegments, segment_ratios=None): """ Split an equivalent borehole into segments. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -412,20 +413,20 @@ def segments(self, n_segments: int, segment_ratios: Optional[NDArray[np.float64] """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - z = self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] + segment_ratios = np.full(nSegments, 1. / nSegments) + z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] segments = [_EquivalentBorehole( - (ratios * self.h, - self.d + z_i * np.cos(self.tilt), + (ratios * self.H, + self.D + z_i * np.cos(self.tilt), self.r_b, self.x + z_i * np.sin(self.tilt) * np.cos(self.orientation), self.y + z_i * np.sin(self.tilt) * np.sin(self.orientation), self.tilt, self.orientation) - ) for z_i, ratios in zip(z, segment_ratios)] + ) for z_i, ratios in zip(z, segment_ratios)] return segments - def unique_distance(self, target: _EquivalentBorehole, dis_tol: float = 0.01) -> Tuple[NDArray[np.float64], NDArray[np.float64]]: + def unique_distance(self, target, disTol=0.01): """ Find unique distances between pairs of boreholes for a pair of equivalent boreholes. @@ -434,7 +435,7 @@ def unique_distance(self, target: _EquivalentBorehole, dis_tol: float = 0.01) -> ---------- target : _EquivalentBorehole object Target borehole for which the distances are evaluated. - dis_tol : float, optional + disTol : float, optional Relative tolerance on radial distance. Two distances (d1, d2) between two pairs of boreholes are considered equal if the difference between the two distances (abs(d1-d2)) is below tolerance. @@ -462,38 +463,38 @@ def unique_distance(self, target: _EquivalentBorehole, dis_tol: float = 0.01) -> """ # Find all distances between the boreholes, sorted and flattened all_dis = np.sort(self.distance(target).flatten()) - n_dis = len(all_dis) + nDis = len(all_dis) # Find unique distances within tolerance - dis: List[float] = [] - w_dis: List[float] = [] + dis = [] + wDis = [] # Start the search at the first distance j0 = 0 j1 = 1 - while j0 < n_dis and j1 > 0: + while j0 < nDis and j1 > 0: # Find the index of the first distance for which the distance is # outside tolerance to the current distance - j1 = np.argmax(all_dis >= (1 + dis_tol) * all_dis[j0]) + j1 = np.argmax(all_dis >= (1 + disTol) * all_dis[j0]) if j1 > j0: # Add the average of the distances within tolerance to the # list of unique distances and store the number of distances dis.append(np.mean(all_dis[j0:j1])) - w_dis.append(j1 - j0) + wDis.append(j1-j0) else: # All remaining distances are within tolerance dis.append(np.mean(all_dis[j0:])) - w_dis.append(n_dis - j0) + wDis.append(nDis-j0) j0 = j1 + + return np.array(dis), np.array(wDis) - return np.array(dis), np.array(w_dis) - - def _segment_edges(self, n_segments: int, segment_ratios: Optional[np.float64] = None) -> float: + def _segment_edges(self, nSegments, segment_ratios=None): """ Linear coordinates of the segment edges. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -511,21 +512,22 @@ def _segment_edges(self, n_segments: int, segment_ratios: Optional[np.float64] = Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1._segment_edges(5) """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - return np.concatenate(([0.], np.cumsum(segment_ratios))) * self.h + segment_ratios = np.full(nSegments, 1. / nSegments) + z = np.concatenate(([0.], np.cumsum(segment_ratios))) * self.H + return z - def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[np.float64] = None) -> float: + def _segment_midpoints(self, nSegments, segment_ratios=None): """ Linear coordinates of the segment midpoints. Parameters ---------- - n_segments : int + nSegments : int Number of segments. segment_ratios : array, optional Ratio of the borehole length represented by each segment. The @@ -543,16 +545,18 @@ def _segment_midpoints(self, n_segments: int, segment_ratios: Optional[np.float6 Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> b1._segment_midpoints(5) """ if segment_ratios is None: - segment_ratios = np.full(n_segments, 1. / n_segments) - return self._segment_edges(n_segments, segment_ratios=segment_ratios)[:-1] + segment_ratios * self.h / 2 + segment_ratios = np.full(nSegments, 1. / nSegments) + z = self._segment_edges(nSegments, segment_ratios=segment_ratios)[:-1] \ + + segment_ratios * self.H / 2 + return z -def find_duplicates(bore_field: List[Borehole], disp: bool = False): +def find_duplicates(boreField, disp=False): """ The distance method :func:`Borehole.distance` is utilized to find all duplicate boreholes in a boreField. @@ -563,7 +567,7 @@ def find_duplicates(bore_field: List[Borehole], disp: bool = False): Parameters ---------- - bore_field : list + boreField : list A list of :class:`Borehole` objects disp : bool, optional Set to true to print progression messages. @@ -575,11 +579,11 @@ def find_duplicates(bore_field: List[Borehole], disp: bool = False): A list of tuples where the tuples are pairs of duplicates """ # Number of boreholes - n = len(bore_field) + n = len(boreField) # Max. borehole radius - r_b = np.max([b.r_b for b in bore_field]) + r_b = np.max([b.r_b for b in boreField]) # Array of coordinates - coordinates = np.array([[b.x, b.y] for b in bore_field]) + coordinates = np.array([[b.x, b.y] for b in boreField]) # Find distance between each pair of boreholes distances = pdist(coordinates, 'euclidean') # Find duplicate boreholes @@ -595,7 +599,7 @@ def find_duplicates(bore_field: List[Borehole], disp: bool = False): return duplicate_pairs -def remove_duplicates(bore_field: List[Borehole], disp=False): +def remove_duplicates(boreField, disp=False): """ Removes all of the duplicates found from the duplicate pairs returned in :func:`check_duplicates`. @@ -605,7 +609,7 @@ def remove_duplicates(bore_field: List[Borehole], disp=False): Parameters ---------- - bore_field : list + boreField : list A list of :class:`Borehole` objects disp : bool, optional Set to true to print progression messages. @@ -613,49 +617,42 @@ def remove_duplicates(bore_field: List[Borehole], disp=False): Returns ------- - new_bore_field : list + new_boreField : list A boreField without duplicates """ # Find duplicate pairs - duplicate_pairs = find_duplicates(bore_field, disp=disp) + duplicate_pairs = find_duplicates(boreField, disp=disp) # Boreholes not to be included duplicate_bores = [pair[1] for pair in duplicate_pairs] # Initialize new borefield - new_bore_field = [b for i, b in enumerate(bore_field) if i not in duplicate_bores] + new_boreField = [b for i, b in enumerate(boreField) if i not in duplicate_bores] if disp: print(' gt.boreholes.remove_duplicates() '.center(50, '-')) - n_duplicates = len(bore_field) - len(new_bore_field) + n_duplicates = len(boreField) - len(new_boreField) print(f'The number of duplicates removed: {n_duplicates}') - return new_bore_field + return new_boreField -def _orientation(x: float, x0: float, y: float, y0: float, r_b: float) -> Optional[float]: - dx = x - x0 - dy = y - y0 - return np.arctan2(dy, dx) if np.sqrt(dx * dx + dy * dy) > r_b else None - - -def rectangle_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., - origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: +def rectangle_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): """ Build a list of boreholes in a rectangular bore field configuration. Parameters ---------- - n_1 : int + N_1 : int Number of borehole in the x direction. - n_2 : int + N_2 : int Number of borehole in the y direction. - b_1 : float + B_1 : float Distance (in meters) between adjacent boreholes in the x direction. - b_2 : float + B_2 : float Distance (in meters) between adjacent boreholes in the y direction. - h : float + H : float Borehole length (in meters). - d : float + D : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -679,7 +676,7 @@ def rectangle_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: flo Examples -------- - >>> boreField = gt.boreholes.rectangle_field(n_1=3, n_2=2, b_1=5., b_2=5., + >>> boreField = gt.boreholes.rectangle_field(N_1=3, N_2=2, B_1=5., B_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -690,30 +687,49 @@ def rectangle_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: flo 0 1 2 """ + borefield = [] - x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = (N_1 - 1) / 2 * B_1 + y0 = (N_2 - 1) / 2 * B_2 + else: + x0, y0 = origin - return [Borehole(h, d, r_b, i * b_1, j * b_2, tilt=tilt, orientation=_orientation(i * b_1, x0, j * b_2, y0, r_b)) for j in range(n_2) for i in range(n_1)] + for j in range(N_2): + for i in range(N_1): + x = i * B_1 + y = j * B_2 + # The borehole is inclined only if it does not lie on the origin + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + orientation = np.arctan2(y - y0, x - x0) + borefield.append( + Borehole( + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + else: + borefield.append(Borehole(H, D, r_b, x, y)) + + return borefield -def l_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., - origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: +def L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): """ Build a list of boreholes in a L-shaped bore field configuration. Parameters ---------- - n_1 : int + N_1 : int Number of borehole in the x direction. - n_2 : int + N_2 : int Number of borehole in the y direction. - b_1 : float + B_1 : float Distance (in meters) between adjacent boreholes in the x direction. - b_2 : float + B_2 : float Distance (in meters) between adjacent boreholes in the y direction. - h : float + H : float Borehole length (in meters). - d : float + D : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -737,7 +753,7 @@ def l_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: floa Examples -------- - >>> boreField = gt.boreholes.l_shaped_field(n_1=3, n_2=2, b_1=5., b_2=5., + >>> boreField = gt.boreholes.L_shaped_field(N_1=3, N_2=2, B_1=5., B_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -748,34 +764,59 @@ def l_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: floa 0 1 2 """ + borefield = [] - x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) - - borefield: List[Borehole] = [Borehole(h, d, r_b, i * b_1, 0, tilt=tilt, orientation=_orientation(i * b_1, x0, 0, y0, r_b)) for i in range(n_1)] - - borefield += [Borehole(h, d, r_b, 0, j * b_2, tilt=tilt, orientation=_orientation(0, x0, j * b_2, y0, r_b)) for j in range(1, n_2)] + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = (N_1 - 1) / 2 * B_1 + y0 = (N_2 - 1) / 2 * B_2 + else: + x0, y0 = origin + + for i in range(N_1): + x = i * B_1 + y = 0. + # The borehole is inclined only if it does not lie on the origin + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + orientation = np.arctan2(y - y0, x - x0) + borefield.append( + Borehole( + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + else: + borefield.append(Borehole(H, D, r_b, x, y)) + for j in range(1, N_2): + x = 0. + y = j * B_2 + # The borehole is inclined only if it does not lie on the origin + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + orientation = np.arctan2(y - y0, x - x0) + borefield.append( + Borehole( + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + else: + borefield.append(Borehole(H, D, r_b, x, y)) return borefield -def u_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., - origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: +def U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0., origin=None): """ Build a list of boreholes in a U-shaped bore field configuration. Parameters ---------- - n_1 : int + N_1 : int Number of borehole in the x direction. - n_2 : int + N_2 : int Number of borehole in the y direction. - b_1 : float + B_1 : float Distance (in meters) between adjacent boreholes in the x direction. - b_2 : float + B_2 : float Distance (in meters) between adjacent boreholes in the y direction. - h : float + H : float Borehole length (in meters). - d : float + D : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -799,7 +840,7 @@ def u_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: floa Examples -------- - >>> boreField = gt.boreholes.u_shaped_field(n_1=3, n_2=2, b_1=5., b_2=5., + >>> boreField = gt.boreholes.U_shaped_field(N_1=3, N_2=2, B_1=5., B_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=3 and N_2=2, the bore @@ -810,65 +851,73 @@ def u_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: floa 0 1 2 """ - borefield: List[Borehole] = [] + borefield = [] - x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = (N_1 - 1) / 2 * B_1 + y0 = (N_2 - 1) / 2 * B_2 + else: + x0, y0 = origin - if n_1 > 2 and n_2 > 1: - for i in range(n_1): - x = i * b_1 + if N_1 > 2 and N_2 > 1: + for i in range(N_1): + x = i * B_1 y = 0. # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - for j in range(1, n_2): + borefield.append(Borehole(H, D, r_b, x, y)) + for j in range(1, N_2): x = 0. - y = j * b_2 + y = j * B_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - x = (n_1 - 1) * b_1 - y = j * b_2 + borefield.append(Borehole(H, D, r_b, x, y)) + x = (N_1 - 1) * B_1 + y = j * B_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - return borefield - return rectangle_field(n_1, n_2, b_1, b_2, h, d, r_b, tilt=tilt, origin=origin) + borefield.append(Borehole(H, D, r_b, x, y)) + else: + borefield = rectangle_field( + N_1, N_2, B_1, B_2, H, D, r_b, tilt=tilt, origin=origin) + + return borefield -def box_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: float, r_b: float, tilt: float = 0., - origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: +def box_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b, tilt=0, origin=None): """ Build a list of boreholes in a box-shaped bore field configuration. Parameters ---------- - n_1 : int + N_1 : int Number of borehole in the x direction. - n_2 : int + N_2 : int Number of borehole in the y direction. - b_1 : float + B_1 : float Distance (in meters) between adjacent boreholes in the x direction. - b_2 : float + B_2 : float Distance (in meters) between adjacent boreholes in the y direction. - h : float + H : float Borehole length (in meters). - d : float + D : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -892,7 +941,7 @@ def box_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: fl Examples -------- - >>> boreField = gt.boreholes.box_shaped_field(n_1=4, n_2=3, b_1=5., b_2=5., + >>> boreField = gt.boreholes.box_shaped_field(N_1=4, N_2=3, B_1=5., B_2=5., H=100., D=2.5, r_b=0.05) The bore field is constructed line by line. For N_1=4 and N_2=3, the bore @@ -907,68 +956,77 @@ def box_shaped_field(n_1: int, n_2: int, b_1: float, b_2: float, h: float, d: fl """ borefield = [] - x0, y0 = origin if origin is not None else ((n_1 - 1) / 2 * b_1, (n_2 - 1) / 2 * b_2) + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = (N_1 - 1) / 2 * B_1 + y0 = (N_2 - 1) / 2 * B_2 + else: + x0, y0 = origin - if n_1 > 2 and n_2 > 2: - for i in range(n_1): - x = i * b_1 + if N_1 > 2 and N_2 > 2: + for i in range(N_1): + x = i * B_1 y = 0. # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - x = i * b_1 - y = (n_2 - 1) * b_2 + borefield.append(Borehole(H, D, r_b, x, y)) + x = i * B_1 + y = (N_2 - 1) * B_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - for j in range(1, n_2 - 1): + borefield.append(Borehole(H, D, r_b, x, y)) + for j in range(1, N_2-1): x = 0. - y = j * b_2 + y = j * B_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - x = (n_1 - 1) * b_1 - y = j * b_2 + borefield.append(Borehole(H, D, r_b, x, y)) + x = (N_1 - 1) * B_1 + y = j * B_2 # The borehole is inclined only if it does not lie on the origin - if np.sqrt((x - x0) ** 2 + (y - y0) ** 2) > r_b: + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: orientation = np.arctan2(y - y0, x - x0) borefield.append( Borehole( - h, d, r_b, x, y, tilt=tilt, orientation=orientation)) + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) else: - borefield.append(Borehole(h, d, r_b, x, y)) - return borefield - return rectangle_field(n_1, n_2, b_1, b_2, h, d, r_b, tilt=tilt, origin=origin) + borefield.append(Borehole(H, D, r_b, x, y)) + else: + borefield = rectangle_field( + N_1, N_2, B_1, B_2, H, D, r_b, tilt=tilt, origin=origin) + + return borefield -def circle_field(n: int, r: float, h: float, d: float, r_b: float, tilt: float = 0., origin: Optional[Tuple[float, float]] = None) -> List[Borehole]: +def circle_field(N, R, H, D, r_b, tilt=0., origin=None): """ Build a list of boreholes in a circular field configuration. Parameters ---------- - n : int + N : int Number of boreholes in the bore field. - r : float + R : float Distance (in meters) of the boreholes from the center of the field. - h : float + H : float Borehole length (in meters). - d : float + D : float Borehole buried depth (in meters). r_b : float Borehole radius (in meters). @@ -992,7 +1050,8 @@ def circle_field(n: int, r: float, h: float, d: float, r_b: float, tilt: float = Examples -------- - >>> boreField = gt.boreholes.circle_field(n=8, r = 5., h=100., d=2.5, r_b=0.05) + >>> boreField = gt.boreholes.circle_field(N=8, R = 5., H=100., D=2.5, + r_b=0.05) The bore field is constructed counter-clockwise. For N=8, the bore field layout is as follows:: @@ -1006,14 +1065,33 @@ def circle_field(n: int, r: float, h: float, d: float, r_b: float, tilt: float = 6 """ + borefield = [] - x0, y0 = origin if origin is not None else (0, 0) + if origin is None: + # When no origin is supplied, compute the origin to be at the center of + # the rectangle + x0 = 0. + y0 = 0. + else: + x0, y0 = origin + + for i in range(N): + x = R * np.cos(2 * pi * i / N) + y = R * np.sin(2 * pi * i / N) + orientation = np.arctan2(y - y0, x - x0) + # The borehole is inclined only if it does not lie on the origin + if np.sqrt((x - x0)**2 + (y - y0)**2) > r_b: + orientation = np.arctan2(y - y0, x - x0) + borefield.append( + Borehole( + H, D, r_b, x, y, tilt=tilt, orientation=orientation)) + else: + borefield.append(Borehole(H, D, r_b, x, y)) - return [Borehole(h, d, r_b, r * np.cos(2 * pi * i / n), r * np.sin(2 * pi * i / n), tilt=tilt, - orientation=_orientation(r * np.cos(2 * pi * i / n), x0, r * np.sin(2 * pi * i / n), y0, r_b)) for i in range(n)] + return borefield -def field_from_file(filename: str) -> List[Borehole]: +def field_from_file(filename): """ Build a list of boreholes given coordinates and dimensions provided in a text file. @@ -1032,7 +1110,7 @@ def field_from_file(filename: str) -> List[Borehole]: ----- The text file should be formatted as follows:: - # x y h d r_b tilt orientation + # x y H D r_b tilt orientation 0. 0. 100. 2.5 0.075 0. 0. 5. 0. 100. 2.5 0.075 0. 0. 0. 5. 100. 2.5 0.075 0. 0. @@ -1047,8 +1125,8 @@ def field_from_file(filename: str) -> List[Borehole]: for line in data: x = line[0] y = line[1] - h = line[2] - d = line[3] + H = line[2] + D = line[3] r_b = line[4] # Previous versions of pygfunction only required up to line[4]. # Now check to see if tilt and orientation exist. @@ -1059,12 +1137,13 @@ def field_from_file(filename: str) -> List[Borehole]: tilt = 0. orientation = 0. borefield.append( - Borehole(h, d, r_b, x=x, y=y, tilt=tilt, orientation=orientation)) + Borehole(H, D, r_b, x=x, y=y, tilt=tilt, orientation=orientation)) return borefield -def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: bool = True, labels: bool = True, show_tilt: bool = True) -> plt.figure: +def visualize_field( + borefield, viewTop=True, view3D=True, labels=True, showTilt=True): """ Plot the top view and 3D view of borehole positions. @@ -1072,16 +1151,16 @@ def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: b ---------- borefield : list List of boreholes in the bore field. - view_top : bool, optional + viewTop : bool, optional Set to True to plot top view. Default is True - view_3d : bool, optional + view3D : bool, optional Set to True to plot 3D view. Default is True labels : bool, optional Set to True to annotate borehole indices to top view plot. Default is True - show_tilt : bool, optional + showTilt : bool, optional Set to True to show borehole inclination on top view plot. Default is True @@ -1095,19 +1174,19 @@ def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: b # Configure figure and axes fig = _initialize_figure() - if view_top and view_3d: + if viewTop and view3D: ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122, projection='3d') - elif view_top: + elif viewTop: ax1 = fig.add_subplot(111) - elif view_3d: + elif view3D: ax2 = fig.add_subplot(111, projection='3d') - if view_top: + if viewTop: ax1.set_xlabel(r'$x$ [m]') ax1.set_ylabel(r'$y$ [m]') ax1.axis('equal') _format_axes(ax1) - if view_3d: + if view3D: ax2.set_xlabel(r'$x$ [m]') ax2.set_ylabel(r'$y$ [m]') ax2.set_zlabel(r'$z$ [m]') @@ -1117,16 +1196,16 @@ def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: b # ------------------------------------------------------------------------- # Top view # ------------------------------------------------------------------------- - if view_top: - i = 0 # Initialize borehole index + if viewTop: + i = 0 # Initialize borehole index for borehole in borefield: # Extract borehole parameters (x, y) = borehole.position() - H = borehole.h + H = borehole.H tilt = borehole.tilt orientation = borehole.orientation # Add current borehole to the figure - if show_tilt: + if showTilt: ax1.plot( [x, x + H * np.sin(tilt) * np.cos(orientation)], [y, y + H * np.sin(tilt) * np.sin(orientation)], @@ -1140,25 +1219,26 @@ def visualize_field(borefield: List[Borehole], view_top: bool = True, view_3d: b # ------------------------------------------------------------------------- # 3D view # ------------------------------------------------------------------------- - if view_3d: + if view3D: for borehole in borefield: # Position of head of borehole (x, y) = borehole.position() # Position of bottom of borehole - x_H = x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation) - y_H = y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation) - z_H = borehole.d + borehole.h * np.cos(borehole.tilt) + x_H = x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation) + y_H = y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation) + z_H = borehole.D + borehole.H*np.cos(borehole.tilt) # Add current borehole to the figure ax2.plot(np.atleast_1d(x), np.atleast_1d(y), - np.atleast_1d(borehole.d), + np.atleast_1d(borehole.D), 'ko') ax2.plot(np.array([x, x_H]), np.array([y, y_H]), - np.array([borehole.d, z_H]), + np.array([borehole.D, z_H]), 'k-') - if view_top and view_3d: + + if viewTop and view3D: plt.tight_layout(rect=[0, 0.0, 0.90, 1.0]) else: plt.tight_layout() diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index 3cb1eae9..99e1d8ad 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -4,12 +4,9 @@ import matplotlib.pyplot as plt import numpy as np -from numpy.typing import NDArray from scipy.cluster.hierarchy import cut_tree, dendrogram, linkage from scipy.constants import pi from scipy.interpolate import interp1d as interp1d -from scipy.sparse import csr_matrix -from scipy.sparse.linalg import spsolve as sp_solve from .boreholes import Borehole, _EquivalentBorehole, find_duplicates from .heat_transfer import finite_line_source, finite_line_source_vectorized, \ @@ -19,15 +16,6 @@ from .utilities import _initialize_figure, _format_axes from . import utilities -from enum import Enum, auto -from typing import Dict, List, Callable, Union, Optional - - -class Method(Enum): - similarities = auto() - detailed = auto() - equivalent = auto() - class gFunction(object): """ @@ -214,26 +202,40 @@ class gFunction(object): Transfer, 127, 105496. """ - def __init__(self, boreholes_or_network, alpha: float, time: Optional[Union[float, NDArray[np.float64]]] = None, - method: Optional[Method] = None, boundary_condition: Optional[str] = None, options: Optional[dict] = None): + def __init__(self, boreholes_or_network, alpha, time=None, + method='equivalent', boundary_condition=None, options={}): self.alpha = alpha self.time = time - self.method = method if method is not None else Method.equivalent + self.method = method self.boundary_condition = boundary_condition - self.options = options if options is not None else {} + self.options = options # Format inputs and assign default values where needed self._format_inputs(boreholes_or_network) # Check the validity of inputs self._check_inputs() + # Load the chosen solver - self.solver = solver_mathing[self.method](self.boreholes, self.network, self.time, self.boundary_condition, **self.options) + if self.method.lower()=='similarities': + self.solver = _Similarities( + self.boreholes, self.network, self.time, + self.boundary_condition, **self.options) + elif self.method.lower()=='detailed': + self.solver = _Detailed( + self.boreholes, self.network, self.time, + self.boundary_condition, **self.options) + elif self.method.lower()=='equivalent': + self.solver = _Equivalent( + self.boreholes, self.network, self.time, + self.boundary_condition, **self.options) + else: + raise ValueError(f"'{method}' is not a valid method.") # If a time vector is provided, evaluate the g-function if self.time is not None: self.gFunc = self.evaluate_g_function(self.time) - def evaluate_g_function(self, time: Union[float, NDArray[np.float64]]): + def evaluate_g_function(self, time): """ Evaluate the g-function. @@ -289,7 +291,7 @@ def visualize_g_function(self): _format_axes(ax) # Borefield characteristic time - ts = np.mean([b.h for b in self.boreholes]) ** 2 / (9. * self.alpha) + ts = np.mean([b.H for b in self.boreholes])**2/(9.*self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Draw g-function @@ -339,7 +341,7 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): _format_axes(ax2) # Borefield characteristic time - ts = np.mean([b.h for b in self.solver.boreholes]) ** 2 / (9. * self.alpha) + ts = np.mean([b.H for b in self.solver.boreholes])**2/(9.*self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Plot curves for requested boreholes @@ -351,11 +353,11 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], - linestyle='--', - marker='None', - color=color) + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + linestyle='--', + marker='None', + color=color) ax1.plot(borehole.x, borehole.y, linestyle='None', @@ -365,11 +367,11 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], - linestyle='--', - marker='None', - color='k') + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], + linestyle='--', + marker='None', + color='k') ax1.plot(borehole.x, borehole.y, linestyle='None', @@ -380,7 +382,8 @@ def visualize_heat_extraction_rates(self, iBoreholes=None, showTilt=True): plt.tight_layout() return fig - def visualize_heat_extraction_rate_profiles(self, time=None, iBoreholes=None, showTilt=True): + def visualize_heat_extraction_rate_profiles( + self, time=None, iBoreholes=None, showTilt=True): """ Plot the heat extraction rate profiles at chosen time. @@ -435,8 +438,8 @@ def visualize_heat_extraction_rate_profiles(self, time=None, iBoreholes=None, sh # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -449,8 +452,8 @@ def visualize_heat_extraction_rate_profiles(self, time=None, iBoreholes=None, sh # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -502,7 +505,7 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): _format_axes(ax2) # Borefield characteristic time - ts = np.mean([b.h for b in self.solver.boreholes]) ** 2 / (9. * self.alpha) + ts = np.mean([b.H for b in self.solver.boreholes])**2/(9.*self.alpha) # Dimensionless time (log) lntts = np.log(self.time/ts) # Plot curves for requested boreholes @@ -514,8 +517,8 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -528,8 +531,8 @@ def visualize_temperatures(self, iBoreholes=None, showTilt=True): # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -597,8 +600,8 @@ def visualize_temperature_profiles( # Draw colored marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color=color) @@ -611,8 +614,8 @@ def visualize_temperature_profiles( # Draw black marker for borehole position if showTilt: ax1.plot( - [borehole.x, borehole.x + borehole.h * np.sin(borehole.tilt) * np.cos(borehole.orientation)], - [borehole.y, borehole.y + borehole.h * np.sin(borehole.tilt) * np.sin(borehole.orientation)], + [borehole.x, borehole.x + borehole.H*np.sin(borehole.tilt)*np.cos(borehole.orientation)], + [borehole.y, borehole.y + borehole.H*np.sin(borehole.tilt)*np.sin(borehole.orientation)], linestyle='--', marker='None', color='k') @@ -690,8 +693,8 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # The heat extraction rate is duplicated to draw from # z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].d, - self.solver.boreholes[i].d + self.solver.boreholes[i].h])) + np.array([self.solver.boreholes[i].D, + self.solver.boreholes[i].D + self.solver.boreholes[i].H])) Q_b.append(np.array(2*[self.solver.Q_b])) else: i0 = self.solver._i0Segments[i] @@ -710,7 +713,7 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # Borehole length ratio at the mid-depth of each segment segment_ratios = self.solver.segment_ratios[i] z.append( - self.solver.boreholes[i].d \ + self.solver.boreholes[i].D \ + self.solver.boreholes[i]._segment_midpoints( self.solver.nBoreSegments[i], segment_ratios=segment_ratios)) @@ -719,8 +722,8 @@ def _heat_extraction_rate_profiles(self, time, iBoreholes): # If there is only one segment, the heat extraction rate is # duplicated to draw from z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].d, - self.solver.boreholes[i].d + self.solver.boreholes[i].h])) + np.array([self.solver.boreholes[i].D, + self.solver.boreholes[i].D + self.solver.boreholes[i].H])) Q_b.append(np.array(2*[np.asscalar(Q_bi)])) return z, Q_b @@ -788,8 +791,8 @@ def _temperature_profiles(self, time, iBoreholes): # boreholes). The temperature is duplicated to draw from # z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].d, - self.solver.boreholes[i].d + self.solver.boreholes[i].h])) + np.array([self.solver.boreholes[i].D, + self.solver.boreholes[i].D + self.solver.boreholes[i].H])) if time is None: # If time is None, temperatures are extracted at the last # time step. @@ -821,7 +824,7 @@ def _temperature_profiles(self, time, iBoreholes): segment_ratios = self.solver.segment_ratios[i] z.append( - self.solver.boreholes[i].d \ + self.solver.boreholes[i].D \ + self.solver.boreholes[i]._segment_midpoints( self.solver.nBoreSegments[i], segment_ratios=segment_ratios)) @@ -830,8 +833,8 @@ def _temperature_profiles(self, time, iBoreholes): # If there is only one segment, the temperature is # duplicated to draw from z = D to z = D + H. z.append( - np.array([self.solver.boreholes[i].d, - self.solver.boreholes[i].d + self.solver.boreholes[i].h])) + np.array([self.solver.boreholes[i].D, + self.solver.boreholes[i].D + self.solver.boreholes[i].H])) T_b.append(np.array(2*[np.asscalar(T_bi)])) return z, T_b @@ -861,13 +864,13 @@ def _format_inputs(self, boreholes_or_network): self.boundary_condition = 'UBWT' # If the 'equivalent' solver is selected for the 'MIFT' condition, # switch to the 'similarities' solver if boreholes are in series - if self.boundary_condition == 'MIFT' and self.method is Method.equivalent: + if self.boundary_condition == 'MIFT' and self.method.lower() == 'equivalent': if not np.all(np.array(self.network.c, dtype=int) == -1): warnings.warn( "\nSolver 'equivalent' is only valid for " "parallel-connected boreholes. Calculations will use the " "'similarities' solver instead.") - self.method = Method.similarities + self.method = 'similarities' elif not ( type(self.network.m_flow_network) is float or ( type(self.network.m_flow_network) is np.ndarray and \ @@ -877,7 +880,7 @@ def _format_inputs(self, boreholes_or_network): "\nSolver 'equivalent' is only valid for equal mass flow " "rates into the boreholes. Calculations will use the " "'similarities' solver instead.") - self.method = Method.similarities + self.method = 'similarities' elif not np.all( [np.allclose(self.network.p[0]._Rd, pipe._Rd) for pipe in self.network.p]): @@ -885,7 +888,7 @@ def _format_inputs(self, boreholes_or_network): "\nSolver 'equivalent' is only valid for boreholes with " "the same piping configuration. Calculations will use " "the 'similarities' solver instead.") - self.method = Method.similarities + self.method = 'similarities' return def _check_inputs(self): @@ -916,9 +919,10 @@ def _check_inputs(self): assert type(self.boundary_condition) is str and self.boundary_condition in acceptable_boundary_conditions, \ f"Boundary condition '{self.boundary_condition}' is not an acceptable boundary condition. \n" \ f"Please provide one of the following inputs : {acceptable_boundary_conditions}" - assert isinstance(self.method, Method), \ + acceptable_methods = ['detailed', 'similarities', 'equivalent'] + assert type(self.method) is str and self.method in acceptable_methods, \ f"Method '{self.method}' is not an acceptable method. \n" \ - f"Please provide one of the following inputs : {[method for method in Method]}" + f"Please provide one of the following inputs : {acceptable_methods}" return @@ -967,8 +971,8 @@ def uniform_heat_extraction(boreholes, time, alpha, use_similarities=True, Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> alpha = 1.0e-6 >>> time = np.array([1.0*10**i for i in range(4, 12)]) >>> gt.gfunction.uniform_heat_extraction([b1, b2], time, alpha) @@ -998,7 +1002,10 @@ def uniform_heat_extraction(boreholes, time, alpha, use_similarities=True, 'dtype':dtype, 'disp':disp} # Select the correct solver: - method=Method.similarities if use_similarities else Method.detailed + if use_similarities: + method='similarities' + else: + method='detailed' # Evaluate g-function gFunc = gFunction( boreholes, alpha, time=time, method=method, @@ -1071,8 +1078,8 @@ def uniform_temperature(boreholes, time, alpha, nSegments=8, Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> alpha = 1.0e-6 >>> time = np.array([1.0*10**i for i in range(4, 12)]) >>> gt.gfunction.uniform_temperature([b1, b2], time, alpha) @@ -1105,9 +1112,9 @@ def uniform_temperature(boreholes, time, alpha, nSegments=8, 'disp':disp} # Select the correct solver: if use_similarities: - method=Method.similarities + method='similarities' else: - method=Method.detailed + method='detailed' # Evaluate g-function gFunc = gFunction( boreholes, alpha, time=time, method=method, @@ -1214,9 +1221,9 @@ def equal_inlet_temperature( 'disp':disp} # Select the correct solver: if use_similarities: - method=Method.similarities + method='similarities' else: - method=Method.detailed + method='detailed' # Evaluate g-function gFunc = gFunction( network, alpha, time=time, method=method, @@ -1295,8 +1302,8 @@ def mixed_inlet_temperature( Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> Utube1 = gt.pipes.SingleUTube(pos=[(-0.05, 0), (0, -0.05)], r_in=0.015, r_out=0.02, borehole=b1,k_s=2, k_g=1, R_fp=0.1) @@ -1340,9 +1347,9 @@ def mixed_inlet_temperature( 'disp':disp} # Select the correct solver: if use_similarities: - method=Method.similarities + method='similarities' else: - method=Method.detailed + method='detailed' # Evaluate g-function gFunc = gFunction( network, alpha, time=time, method=method, @@ -1421,13 +1428,13 @@ class _BaseSolver(object): Default is numpy.double. """ - def __init__(self, boreholes: list, network: Network, time: Union[float, NDArray[np.float64]], boundary_condition: str, - nSegments: int = 8, segment_ratios: Optional[Union[NDArray[np.float64], Callable]]=utilities.segment_ratios, - approximate_FLS:bool=False, mQuad: int=11, nFLS: int=10, disp:bool=False, - profiles:bool=False, kind:str='linear', dtype:float=np.double, + def __init__(self, boreholes, network, time, boundary_condition, + nSegments=8, segment_ratios=utilities.segment_ratios, + approximate_FLS=False, mQuad=11, nFLS=10, disp=False, + profiles=False, kind='linear', dtype=np.double, **other_options): - self.boreholes: list = boreholes - self.network: Network = network + self.boreholes = boreholes + self.network = network # Convert time to a 1d array self.time = np.atleast_1d(time).flatten() self.boundary_condition = boundary_condition @@ -1439,10 +1446,10 @@ def __init__(self, boreholes: list, network: Network, time: Union[float, NDArray self.nBoreSegments = nSegments if isinstance(segment_ratios, np.ndarray): segment_ratios = [segment_ratios] * nBoreholes + elif segment_ratios is None: + segment_ratios = [np.full(n, 1./n) for n in self.nBoreSegments] elif callable(segment_ratios): segment_ratios = [segment_ratios(n) for n in self.nBoreSegments] - else: - segment_ratios = [np.full(n, 1./n) for n in self.nBoreSegments] self.segment_ratios = segment_ratios # Shortcut for segment_ratios comparisons self._equal_segment_ratios = \ @@ -1455,8 +1462,10 @@ def __init__(self, boreholes: list, network: Network, time: Union[float, NDArray rtol=1e-6) for segment_ratios in self.segment_ratios] # Find indices of first and last segments along boreholes - self._i0Segments = [sum(self.nBoreSegments[0:i]) for i in range(nBoreholes)] - self._i1Segments = [sum(self.nBoreSegments[0:(i + 1)]) for i in range(nBoreholes)] + self._i0Segments = [sum(self.nBoreSegments[0:i]) + for i in range(nBoreholes)] + self._i1Segments = [sum(self.nBoreSegments[0:(i + 1)]) + for i in range(nBoreholes)] self.approximate_FLS = approximate_FLS self.mQuad = mQuad self.nFLS = nFLS @@ -1467,11 +1476,11 @@ def __init__(self, boreholes: list, network: Network, time: Union[float, NDArray # Check the validity of inputs self._check_inputs() # Initialize the solver with solver-specific options - self.nSources = self.initialize(*other_options) + self.nSources = self.initialize(**other_options) return - def initialize(self, *kwargs) -> int: + def initialize(self, *kwargs): """ Perform any calculation required at the initialization of the solver and returns the number of finite line heat sources in the borefield. @@ -1493,6 +1502,7 @@ def initialize(self, *kwargs) -> int: 'return the number of finite line heat sources in the borefield ' 'used to initialize the matrix of segment-to-segment thermal ' 'response factors (of size: nSources x nSources)') + return None def solve(self, time, alpha): """ @@ -1530,7 +1540,7 @@ def solve(self, time, alpha): # Segment lengths H_b = self.segment_lengths() if self.boundary_condition == 'MIFT': - Hb_individual = np.array([b.h for b in self.boreSegments], dtype=self.dtype) + Hb_individual = np.array([b.H for b in self.boreSegments], dtype=self.dtype) H_tot = np.sum(H_b) if self.disp: print('Building and solving the system of equations ...', end='') @@ -1584,10 +1594,8 @@ def solve(self, time, alpha): A = np.block([[h_dt, -np.ones((self.nSources, 1), dtype=self.dtype)], [H_b, 0.]]) - # A = csr_matrix(A) B = np.hstack((-T_b0, H_tot)) # Solve the system of equations - # X = sp_solve(A, B) X = np.linalg.solve(A, B) # Store calculated heat extraction rates Q_b[:,p] = X[0:self.nSources] @@ -1670,7 +1678,7 @@ def segment_lengths(self): """ # Borehole lengths - H_b = np.array([b.h for b in self.boreSegments], dtype=self.dtype) + H_b = np.array([b.H for b in self.boreSegments], dtype=self.dtype) return H_b def borehole_segments(self): @@ -1688,7 +1696,8 @@ def borehole_segments(self): """ boreSegments = [] # list for storage of boreSegments for b, nSegments, segment_ratios in zip(self.boreholes, self.nBoreSegments, self.segment_ratios): - boreSegments.extend(b.segments(nSegments, segment_ratios=segment_ratios)) + segments = b.segments(nSegments, segment_ratios=segment_ratios) + boreSegments.extend(segments) return boreSegments @@ -1936,7 +1945,8 @@ def initialize(self, **kwargs): """ # Split boreholes into segments self.boreSegments = self.borehole_segments() - return len(self.boreSegments) + nSources = len(self.boreSegments) + return nSources def thermal_response_factors(self, time, alpha, kind='linear'): """ @@ -2017,8 +2027,7 @@ def thermal_response_factors(self, time, alpha, kind='linear'): h_ij = interp1d(np.hstack((0., time)), h_ij, kind=kind, copy=True, axis=2) toc = perf_counter() - if self.disp: - print(f' {toc - tic:.3f} sec') + if self.disp: print(f' {toc - tic:.3f} sec') return h_ij @@ -2057,8 +2066,8 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): # Unpack parameters x = np.array([b.x for b in self.boreSegments]) y = np.array([b.y for b in self.boreSegments]) - H = np.array([b.h for b in self.boreSegments]) - D = np.array([b.d for b in self.boreSegments]) + H = np.array([b.H for b in self.boreSegments]) + D = np.array([b.D for b in self.boreSegments]) r_b = np.array([b.r_b for b in self.boreSegments]) # Distances between boreholes dis = np.maximum( @@ -2083,6 +2092,7 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): return h, i_segment, j_segment + class _Similarities(_BaseSolver): """ Similarities solver for the evaluation of the g-function. @@ -2606,9 +2616,9 @@ def _compare_boreholes(self, borehole1, borehole2): """ # Compare lengths (H), buried depth (D) and radius (r_b) - if (abs((borehole1.h - borehole2.h) / borehole1.h) < self.tol and + if (abs((borehole1.H - borehole2.H)/borehole1.H) < self.tol and abs((borehole1.r_b - borehole2.r_b)/borehole1.r_b) < self.tol and - abs((borehole1.d - borehole2.d) / (borehole1.d + 1e-30)) < self.tol and + abs((borehole1.D - borehole2.D)/(borehole1.D + 1e-30)) < self.tol and abs(abs(borehole1.tilt) - abs(borehole2.tilt))/(abs(borehole1.tilt) + 1e-30) < self.tol): similarity = True else: @@ -2633,14 +2643,14 @@ def _compare_real_pairs_vertical(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - deltaD1 = pair1[1].d - pair1[0].d - deltaD2 = pair2[1].d - pair2[0].d + deltaD1 = pair1[1].D - pair1[0].D + deltaD2 = pair2[1].D - pair2[0].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of lengths in each pair - equal_H = abs((pair1[0].h - pair1[1].h) / pair1[0].h) < self.tol + equal_H = abs((pair1[0].H - pair1[1].H)/pair1[0].H) < self.tol # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/abs(deltaD1 + 1e-30) < self.tol # Equality of buried depths differences if all boreholes have the same @@ -2670,12 +2680,12 @@ def _compare_image_pairs_vertical(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - sumD1 = pair1[1].d + pair1[0].d - sumD2 = pair2[1].d + pair2[0].d + sumD1 = pair1[1].D + pair1[0].D + sumD2 = pair2[1].D + pair2[0].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of buried depths sums cond_sumD = abs((sumD1 - sumD2)/(sumD1 + 1e-30)) < self.tol if cond_H and cond_sumD: @@ -2732,10 +2742,10 @@ def _compare_real_pairs_inclined(self, pair1, pair2): dy1 = pair1[0].y - pair1[1].y; dy2 = pair2[0].y - pair2[1].y dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) - deltaD1 = pair1[0].d - pair1[1].d; deltaD2 = pair2[0].d - pair2[1].d + deltaD1 = pair1[0].D - pair1[1].D; deltaD2 = pair2[0].D - pair2[1].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/(abs(deltaD1) + 1e-30) < self.tol # Equality of distances @@ -2780,10 +2790,10 @@ def _compare_image_pairs_inclined(self, pair1, pair2): dy1 = pair1[0].y - pair1[1].y; dy2 = pair2[0].y - pair2[1].y dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) - sumD1 = pair1[0].d + pair1[1].d; sumD2 = pair2[0].d + pair2[1].d + sumD1 = pair1[0].D + pair1[1].D; sumD2 = pair2[0].D + pair2[1].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of buried depths sums cond_sumD = abs(sumD1 - sumD2)/(abs(sumD1) + 1e-30) < self.tol # Equality of distances @@ -2835,12 +2845,12 @@ def _compare_realandimage_pairs_inclined(self, pair1, pair2): dis1 = np.sqrt(dx1**2 + dy1**2); dis2 = np.sqrt(dx2**2 + dy2**2) theta_12_1 = np.arctan2(dy1, dx1); theta_12_2 = np.arctan2(dy2, dx2) # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of buried depths cond_D = ( - abs(pair1[0].d - pair2[0].d) / (abs(pair1[0].d) + 1e-30) < self.tol - and abs(pair1[1].d - pair2[1].d) / (abs(pair1[1].d) + 1e-30) < self.tol) + abs(pair1[0].D - pair2[0].D)/(abs(pair1[0].D) + 1e-30) < self.tol + and abs(pair1[1].D - pair2[1].D)/(abs(pair1[1].D) + 1e-30) < self.tol) # Equality of distances cond_dis = abs(dis1 - dis2)/(abs(dis1) + 1e-30) < self.disTol # Equality of tilts @@ -3129,10 +3139,10 @@ def _map_axial_segment_pairs_vertical( # depths else: k_pair[p] = nPairs - H1.append(segment_i.h) - H2.append(segment_j.h) - D1.append(segment_i.d) - D2.append(segment_j.d) + H1.append(segment_i.H) + H2.append(segment_j.H) + D1.append(segment_i.D) + D2.append(segment_j.D) unique_pairs.append((ii, jj)) nPairs += 1 p += 1 @@ -3254,14 +3264,14 @@ def _map_axial_segment_pairs_inclined( rb1.append(segment_i.r_b) x1.append(segment_i.x) y1.append(segment_i.y) - H1.append(segment_i.h) - D1.append(segment_i.d) + H1.append(segment_i.H) + D1.append(segment_i.D) tilt1.append(segment_i.tilt) orientation1.append(segment_i.orientation) x2.append(segment_j.x) y2.append(segment_j.y) - H2.append(segment_j.h) - D2.append(segment_j.d) + H2.append(segment_j.H) + D2.append(segment_j.D) tilt2.append(segment_j.tilt) orientation2.append(segment_j.orientation) unique_pairs.append((ii, jj)) @@ -3763,7 +3773,7 @@ def segment_lengths(self): """ # Borehole lengths - H = np.array([seg.h * seg.nBoreholes + H = np.array([seg.H*seg.nBoreholes for (borehole, nSegments, ratios) in zip( self.boreholes, self.nBoreSegments, @@ -3792,9 +3802,9 @@ def _compare_boreholes(self, borehole1, borehole2): """ # Compare lengths (H), buried depth (D) and radius (r_b) - if (abs((borehole1.h - borehole2.h) / borehole1.h) < self.tol and + if (abs((borehole1.H - borehole2.H)/borehole1.H) < self.tol and abs((borehole1.r_b - borehole2.r_b)/borehole1.r_b) < self.tol and - abs((borehole1.d - borehole2.d) / (borehole1.d + 1e-30)) < self.tol): + abs((borehole1.D - borehole2.D)/(borehole1.D + 1e-30)) < self.tol): similarity = True else: similarity = False @@ -3818,14 +3828,14 @@ def _compare_real_pairs(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - deltaD1 = pair1[1].d - pair1[0].d - deltaD2 = pair2[1].d - pair2[0].d + deltaD1 = pair1[1].D - pair1[0].D + deltaD2 = pair2[1].D - pair2[0].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of lengths in each pair - equal_H = abs((pair1[0].h - pair1[1].h) / pair1[0].h) < self.tol + equal_H = abs((pair1[0].H - pair1[1].H)/pair1[0].H) < self.tol # Equality of buried depths differences cond_deltaD = abs(deltaD1 - deltaD2)/abs(deltaD1 + 1e-30) < self.tol # Equality of buried depths differences if all boreholes have the same @@ -3855,12 +3865,12 @@ def _compare_image_pairs(self, pair1, pair2): True if the two pairs have the same FLS solution. """ - sumD1 = pair1[1].d + pair1[0].d - sumD2 = pair2[1].d + pair2[0].d + sumD1 = pair1[1].D + pair1[0].D + sumD2 = pair2[1].D + pair2[0].D # Equality of lengths between pairs - cond_H = (abs((pair1[0].h - pair2[0].h) / pair1[0].h) < self.tol - and abs((pair1[1].h - pair2[1].h) / pair1[1].h) < self.tol) + cond_H = (abs((pair1[0].H - pair2[0].H)/pair1[0].H) < self.tol + and abs((pair1[1].H - pair2[1].H)/pair1[1].H) < self.tol) # Equality of buried depths sums cond_sumD = abs((sumD1 - sumD2)/(sumD1 + 1e-30)) < self.tol if cond_H and cond_sumD: @@ -4081,7 +4091,7 @@ def _map_axial_segment_pairs(self, iBor, jBor, elif reaSource: # Find segment pairs for the real FLS solution compare_pairs = self._compare_real_pairs - else: + elif imgSource: # Find segment pairs for the image FLS solution compare_pairs = self._compare_image_pairs # Dive both boreholes into segments @@ -4123,10 +4133,10 @@ def _map_axial_segment_pairs(self, iBor, jBor, # depths else: k_pair[p] = nPairs - H1.append(segment_i.h) - H2.append(segment_j.h) - D1.append(segment_i.d) - D2.append(segment_j.d) + H1.append(segment_i.H) + H2.append(segment_j.H) + D1.append(segment_i.D) + D2.append(segment_j.D) unique_pairs.append((i, j)) nPairs += 1 p += 1 @@ -4166,9 +4176,8 @@ def _check_solver_specific_inputs(self): self.network.m_flow_network[0]*len(self.network.b) # Verify that all boreholes have the same piping configuration # This is best done by comparing the matrix of thermal resistances. - assert np.all([np.allclose(self.network.p[0]._Rd, pipe._Rd) for pipe in self.network.p]), \ + assert np.all( + [np.allclose(self.network.p[0]._Rd, pipe._Rd) + for pipe in self.network.p]), \ "All boreholes must have the same piping configuration." return - - -solver_mathing: Dict[Method, Callable] = {Method.similarities: _Similarities, Method.detailed: _Detailed, Method.equivalent: _Equivalent} diff --git a/pygfunction/heat_transfer.py b/pygfunction/heat_transfer.py index 569f174d..6ebd92d3 100644 --- a/pygfunction/heat_transfer.py +++ b/pygfunction/heat_transfer.py @@ -165,8 +165,8 @@ def finite_line_source( Examples -------- - >>> b1 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=0., y=0.) - >>> b2 = gt.boreholes.Borehole(h=150., d=4., r_b=0.075, x=5., y=0.) + >>> b1 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=0., y=0.) + >>> b2 = gt.boreholes.Borehole(H=150., D=4., r_b=0.075, x=5., y=0.) >>> h = gt.heat_transfer.finite_line_source(4*168*3600., 1.0e-6, b1, b2) h = 0.0110473635393 >>> h = gt.heat_transfer.finite_line_source( @@ -202,8 +202,8 @@ def finite_line_source( """ if isinstance(borehole1, Borehole) and isinstance(borehole2, Borehole): # Unpack parameters - H1, D1 = borehole1.h, borehole1.d - H2, D2 = borehole2.h, borehole2.d + H1, D1 = borehole1.H, borehole1.D + H2, D2 = borehole2.H, borehole2.D if borehole1.is_vertical() and borehole2.is_vertical(): # Boreholes are vertical dis = borehole1.distance(borehole2) @@ -290,10 +290,10 @@ def finite_line_source( dis = np.maximum( np.sqrt(np.add.outer(x2, -x1)**2 + np.add.outer(y2, -y1)**2), r_b) - D1 = np.array([b.d for b in borehole1]).reshape(1, -1) - H1 = np.array([b.h for b in borehole1]).reshape(1, -1) - D2 = np.array([b.d for b in borehole2]).reshape(-1, 1) - H2 = np.array([b.h for b in borehole2]).reshape(-1, 1) + D1 = np.array([b.D for b in borehole1]).reshape(1, -1) + H1 = np.array([b.H for b in borehole1]).reshape(1, -1) + D2 = np.array([b.D for b in borehole2]).reshape(-1, 1) + H2 = np.array([b.H for b in borehole2]).reshape(-1, 1) if (np.all([b.is_vertical() for b in borehole1]) and np.all([b.is_vertical() for b in borehole2])): @@ -1119,7 +1119,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) + f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erf_int(q * s)) elif reaSource: # Real FLS solution p = np.array([1, -1, 1, -1]) @@ -1128,7 +1128,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 - D1 - H1, D2 - D1 + H2 - H1], axis=-1) - f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) + f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erf_int(q * s)) elif imgSource: # Image FLS solution p = np.array([1, -1, 1, -1]) @@ -1137,7 +1137,7 @@ def _finite_line_source_integrand(dis, H1, D1, H2, D2, reaSource, imgSource): D2 + D1 + H1, D2 + D1 + H2 + H1], axis=-1) - f = lambda s: 1/(s*s) * np.exp(-dis*dis*s*s) * np.inner(p, erf_int(q * s)) + f = lambda s: s**-2 * np.exp(-dis**2*s**2) * np.inner(p, erf_int(q * s)) else: # No heat source f = lambda s: np.zeros(np.broadcast_shapes( diff --git a/pygfunction/media.py b/pygfunction/media.py index 6bdd2b7a..18b88dde 100644 --- a/pygfunction/media.py +++ b/pygfunction/media.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- -from CoolProp.CoolProp import PropsSI -import warnings +from scp.ethyl_alcohol import EthylAlcohol +from scp.ethylene_glycol import EthyleneGlycol +from scp.methyl_alcohol import MethylAlcohol +from scp.propylene_glycol import PropyleneGlycol +from scp.water import Water class Fluid: @@ -9,7 +12,7 @@ class Fluid: Parameters ---------- - mixer: str + fluid_str: str The mixer for this application should be one of: - 'Water' - Complete water solution - 'MEG' - Ethylene glycol mixed with water @@ -22,63 +25,61 @@ class Fluid: T: float, optional The temperature of the fluid (in Celcius). Default is 20 degC. - P: float, optional - The pressure of the fluid (in Pa). - Default is 101325 Pa. Examples -------- >>> import pygfunction as gt >>> T_f = 20. # Temp at 20 C - >>> gage_P = 20 # PsiG - >>> atm_P = 14.69595 - >>> P = (gage_P + atm_P) * 6894.75728 # Pressure in Pa >>> # complete water solution >>> mix = 'Water' >>> percent = 0 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f, P=P) + >>> fluid = gt.media.Fluid(mix, percent, T=T_f) >>> print(fluid) >>> # 20 % propylene glycol mixed with water >>> mix = 'MPG' >>> percent = 20 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f, P=P) + >>> fluid = gt.media.Fluid(mix, percent, T=T_f) >>> # 60% ethylene glycol mixed with water >>> mix = 'MEG' >>> percent = 60 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f, P=P) + >>> fluid = gt.media.Fluid(mix, percent, T=T_f) >>> print(fluid) - >>> # 5% methanol mixed with water water + >>> # 5% methanol mixed with water >>> mix = 'MMA' >>> percent = 5 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f, P=P) + >>> fluid = gt.media.Fluid(mix, percent, T=T_f) >>> print(fluid) >>> # ethanol / water >>> mix = 'MEA' >>> percent = 10 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f, P=P) + >>> fluid = gt.media.Fluid(mix, percent, T=T_f) >>> print(fluid) """ - def __init__(self, mixer: str, percent: float, - T: float = 20., P: float = 101325.): - if mixer == 'Water': - self.fluid_mix = mixer - elif mixer in ['MEG', 'MPG', 'MMA', 'MEA']: # Expected brines - self.fluid_mix = f'INCOMP::{mixer}-{str(percent)}%' + def __init__(self, fluid_str: str, percent: float, T: float = 20.): + # concentration fraction + x_frac = percent / 100 + + if fluid_str.upper() == 'WATER': + self.fluid = Water() + elif fluid_str.upper() in ['PROPYLENEGLYCOL', 'MPG']: + self.fluid = PropyleneGlycol(x_frac) + elif fluid_str.upper() in ['ETHYLENEGLYCOL', 'MEG']: + self.fluid = EthyleneGlycol(x_frac) + elif fluid_str.upper() in ['METHYLALCOHOL', 'MMA']: + self.fluid = MethylAlcohol(x_frac) + elif fluid_str.upper() in ['ETHYLALCOHOL', 'MEA']: + self.fluid = EthylAlcohol(x_frac) else: - warnings.warn('It is unknown whether or not cool props has the ' - 'mixing fluid requested, proceed with caution.') + raise ValueError(f'Unsupported fluid mixture: "{fluid_str}".') + # Initialize all fluid properties # Temperature of the fluid (in Celsius) self.T_C = T - # Temperature of the fluid (in Kelvin) - self.T_K = T + 273.15 - # Pressure of the fluid (in Pa) - self.P = P # Density (in kg/m3) self.rho = self.density() # Dynamic viscosity (in Pa.s, or N.s/m2) @@ -117,7 +118,7 @@ def density(self): Density (in kg/m3). """ - return PropsSI('D', 'T', self.T_K, 'P', self.P, self.fluid_mix) + return self.fluid.density(self.T_C) def dynamic_viscosity(self): """ @@ -129,7 +130,7 @@ def dynamic_viscosity(self): Dynamic viscosity (in Pa.s, or N.s/m2). """ - return PropsSI('V', 'T', self.T_K, 'P', self.P, self.fluid_mix) + return self.fluid.viscosity(self.T_C) def kinematic_viscosity(self): """ @@ -153,7 +154,7 @@ def specific_heat_capacity(self): Specific isobaric heat capacity (J/kg.K). """ - return PropsSI('C', 'T', self.T_K, 'P', self.P, self.fluid_mix) + return self.fluid.specific_heat(self.T_C) def volumetric_heat_capacity(self): """ @@ -177,7 +178,7 @@ def thermal_conductivity(self): Thermal conductivity (in W/m.K). """ - return PropsSI('L', 'T', self.T_K, 'P', self.P, self.fluid_mix) + return self.fluid.conductivity(self.T_C) def Prandlt_number(self): """ @@ -189,4 +190,4 @@ def Prandlt_number(self): Prandlt number. """ - return PropsSI('PRANDTL', 'T', self.T_K, 'P', self.P, self.fluid_mix) + return self.fluid.prandtl(self.T_C) diff --git a/pygfunction/networks.py b/pygfunction/networks.py index 9837b158..002e8c86 100644 --- a/pygfunction/networks.py +++ b/pygfunction/networks.py @@ -71,7 +71,7 @@ def __init__(self, boreholes, pipes, bore_connectivity=None, m_flow_network=None, cp_f=None, nSegments=None, segment_ratios=None): self.b = boreholes - self.H_tot = sum([b.h for b in self.b]) + self.H_tot = sum([b.H for b in self.b]) self.nBoreholes = len(boreholes) self.p = pipes if bore_connectivity is None: @@ -1183,7 +1183,7 @@ class _EquivalentNetwork(Network): def __init__(self, equivalentBoreholes, pipes, m_flow_network=None, cp_f=None, nSegments=None, segment_ratios=None): self.b = equivalentBoreholes - self.H_tot = sum([b.h * b.nBoreholes for b in self.b]) + self.H_tot = sum([b.H*b.nBoreholes for b in self.b]) self.nBoreholes = len(equivalentBoreholes) self.wBoreholes = np.array( [[b.nBoreholes for b in equivalentBoreholes]]).T diff --git a/pygfunction/pipes.py b/pygfunction/pipes.py index a8006724..68729ebe 100644 --- a/pygfunction/pipes.py +++ b/pygfunction/pipes.py @@ -657,7 +657,7 @@ def effective_borehole_thermal_resistance(self, m_flow_borehole, cp_f): a_Q = self.coefficients_borehole_heat_extraction_rate( m_flow_borehole, cp_f, nSegments=1)[0].item() # Borehole length - H = self.b.h + H = self.b.H # Effective borehole thermal resistance R_b = -0.5*H*(1. + a_out)/a_Q return R_b @@ -1093,8 +1093,8 @@ def _continuity_condition_base( m_flow_borehole, cp_f, nSegments, segment_ratios) # Evaluate coefficient matrices from Hellstrom (1991): - a_in = ((self._f1(self.b.h) + self._f2(self.b.h)) - / (self._f3(self.b.h) - self._f2(self.b.h))) + a_in = ((self._f1(self.b.H) + self._f2(self.b.H)) + / (self._f3(self.b.H) - self._f2(self.b.H))) a_in = np.array([[a_in]]) a_out = np.array([[1.0]]) @@ -1106,7 +1106,7 @@ def _continuity_condition_base( dF4 = F4[:-1] - F4[1:] F5 = self._F5(z) dF5 = F5[:-1] - F5[1:] - a_b[0, :] = (dF4 + dF5) / (self._f3(self.b.h) - self._f2(self.b.h)) + a_b[0, :] = (dF4 + dF5) / (self._f3(self.b.H) - self._f2(self.b.H)) return a_in, a_out, a_b @@ -1742,7 +1742,7 @@ def _continuity_condition( Dm1 = self._Dm1 # Matrix exponential at depth (z = H) - H = self.b.h + H = self.b.H E = np.real(V @ np.diag(np.exp(L*H)) @ Vm1) # Coefficient matrix for borehole wall temperatures @@ -2658,7 +2658,7 @@ def borehole_thermal_resistance(pipe, m_flow_borehole, cp_f): a_Q = pipe.coefficients_borehole_heat_extraction_rate( m_flow_borehole, cp_f, nSegments=1)[0].item() # Borehole length - H = pipe.b.h + H = pipe.b.H # Effective borehole thermal resistance R_b = -0.5*H*(1. + a_out)/a_Q diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index bf38026f..7acd2f68 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -22,10 +22,7 @@ def cardinal_point(direction): return compass[direction] -sqrt_pi = 1 / np.sqrt(np.pi) - - -def erf_int(x: np.ndarray): +def erf_int(x): """ Integral of the error function. @@ -40,36 +37,7 @@ def erf_int(x: np.ndarray): Integral of the error function. """ - - abs_x = np.abs(x) - y_new = abs_x-sqrt_pi - idx = abs_x < 4 - abs_2 = abs_x[idx] - y_new[idx] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi - return y_new - - -def erf_int_old(x: np.ndarray): - """ - Integral of the error function. - - Parameters - ---------- - x : float or array - Argument. - - Returns - ------- - float or array - Integral of the error function. - - """ - return x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) - - -def erf_approx(abs_x: np.ndarray, exp_x2: np.ndarray) -> np.ndarray: - t = 1/(1+0.3275911*abs_x) - return abs_x*(1-(((((1.061405429*t-1.453152027)*t+1.42141374)*t-0.284496736)*t+0.254829592)*t)*exp_x2) + return x * erf(x) - 1.0 / np.sqrt(np.pi) * (1.0 - np.exp(-x**2)) def exp1(x): @@ -388,7 +356,7 @@ def time_geometric(dt, tmax, Nt): return time -def _initialize_figure() -> plt.figure: +def _initialize_figure(): """ Initialize a matplotlib figure object with overwritten default parameters. diff --git a/pyproject.toml b/pyproject.toml index 32d1323a..0a9589bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -# addopts = "--cov=pygfunction" +addopts = "--cov=pygfunction" testpaths = [ "tests", ] diff --git a/requirements.txt b/requirements.txt index c8b33d9b..c889b5d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ numpy scipy matplotlib -CoolProp +SecondaryCoolantProps diff --git a/setup.cfg b/setup.cfg index efb2beec..31273b2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,10 +24,10 @@ classifiers = [options] packages = pygfunction install_requires = - coolprop >= 6.4.1 matplotlib >= 3.5.1 numpy >= 1.21.5 scipy >= 1.7.3 + secondarycoolantprops >= 1.1 python_requires = >=3.7 [options.extras_require] diff --git a/tests/boreholes_test.py b/tests/boreholes_test.py index 3665b50e..ebc3c5e9 100644 --- a/tests/boreholes_test.py +++ b/tests/boreholes_test.py @@ -23,8 +23,8 @@ def test_borehole_init(): borehole = gt.boreholes.Borehole( H, D, r_b, x, y, tilt=tilt, orientation=orientation) assert np.all( - [H == borehole.h, - D == borehole.d, + [H == borehole.H, + D == borehole.D, r_b == borehole.r_b, x == borehole.x, y == borehole.y, @@ -87,8 +87,8 @@ def test_rectangular_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 * N_2, - np.allclose(H, [b.h for b in field]), - np.allclose(D, [b.d for b in field]), + np.allclose(H, [b.H for b in field]), + np.allclose(D, [b.D for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -107,7 +107,7 @@ def test_L_shaped_field(N_1, N_2, B_1, B_2): D = 4. # Borehole buried depth [m] r_b = 0.075 # Borehole radius [m] # Generate the bore field - field = gt.boreholes.l_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) + field = gt.boreholes.L_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) @@ -116,8 +116,8 @@ def test_L_shaped_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 + N_2 - 1, - np.allclose(H, [b.h for b in field]), - np.allclose(D, [b.d for b in field]), + np.allclose(H, [b.H for b in field]), + np.allclose(D, [b.D for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -136,7 +136,7 @@ def test_U_shaped_field(N_1, N_2, B_1, B_2): D = 4. # Borehole buried depth [m] r_b = 0.075 # Borehole radius [m] # Generate the bore field - field = gt.boreholes.u_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) + field = gt.boreholes.U_shaped_field(N_1, N_2, B_1, B_2, H, D, r_b) # Evaluate the borehole to borehole distances x = np.array([b.x for b in field]) y = np.array([b.y for b in field]) @@ -145,8 +145,8 @@ def test_U_shaped_field(N_1, N_2, B_1, B_2): ~np.eye(len(field), dtype=bool)] assert np.all( [len(field) == N_1 + 2 * N_2 - 2 if N_1 > 1 else N_2, - np.allclose(H, [b.h for b in field]), - np.allclose(D, [b.d for b in field]), + np.allclose(H, [b.H for b in field]), + np.allclose(D, [b.D for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -182,8 +182,8 @@ def test_box_shaped_field(N_1, N_2, B_1, B_2): nBoreholes_expected = 2 * (N_1 - 1) + 2 * (N_2 - 1) assert np.all( [len(field) == nBoreholes_expected, - np.allclose(H, [b.h for b in field]), - np.allclose(D, [b.d for b in field]), + np.allclose(H, [b.H for b in field]), + np.allclose(D, [b.D for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), min(B_1, B_2)), ]) @@ -211,8 +211,8 @@ def test_circle_field(N, R): B_min = 2 * R * np.sin(np.pi / N) assert np.all( [len(field) == N, - np.allclose(H, [b.h for b in field]), - np.allclose(D, [b.d for b in field]), + np.allclose(H, [b.H for b in field]), + np.allclose(D, [b.D for b in field]), np.allclose(r_b, [b.r_b for b in field]), len(field) == 1 or np.isclose(np.min(dis), B_min), len(field) == 1 or np.max(dis) <= (2 + 1e-6) * R, diff --git a/tests/gfunction_test.py b/tests/gfunction_test.py index 4103eb55..2cfab47f 100644 --- a/tests/gfunction_test.py +++ b/tests/gfunction_test.py @@ -5,7 +5,6 @@ import pytest import pygfunction as gt -from pygfunction.gfunction import Method # ============================================================================= @@ -15,53 +14,53 @@ # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole', Method.equivalent, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', Method.equivalent, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', 'equivalent', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', 'equivalent', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'equivalent' solver - uniform segments - ('single_borehole', Method.equivalent, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', Method.equivalent, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', 'equivalent', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', 'equivalent', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole', Method.equivalent, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', Method.equivalent, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([10.8993464, 17.09872924, 19.0794071])), + ('single_borehole', 'equivalent', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', 'equivalent', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments_approx', np.array([10.8993464, 17.09872924, 19.0794071])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole', Method.equivalent, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', Method.equivalent, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([10.96117468, 17.2450427, 19.25351959])), + ('single_borehole', 'equivalent', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', 'equivalent', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments_approx', np.array([10.96117468, 17.2450427, 19.25351959])), # 'similarities' solver - unequal segments - ('single_borehole', Method.similarities, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', Method.similarities, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', 'similarities', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', 'similarities', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', 'similarities', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'similarities' solver - uniform segments - ('single_borehole', Method.similarities, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', Method.similarities, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', 'similarities', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', 'similarities', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', 'similarities', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole', Method.similarities, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', Method.similarities, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), + ('single_borehole', 'similarities', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', 'similarities', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', 'similarities', 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole', Method.similarities, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', Method.similarities, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), + ('single_borehole', 'similarities', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', 'similarities', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', 'similarities', 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), # 'detailed' solver - unequal segments - ('single_borehole', Method.detailed, 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), - ('single_borehole_short', Method.detailed, 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), - ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), + ('single_borehole', 'detailed', 'unequal_segments', np.array([5.59717446, 6.36257605, 6.60517223])), + ('single_borehole_short', 'detailed', 'unequal_segments', np.array([4.15784411, 4.98477603, 5.27975732])), + ('ten_boreholes_rectangular', 'detailed', 'unequal_segments', np.array([10.89935004, 17.09864925, 19.0795435])), # 'detailed' solver - uniform segments - ('single_borehole', Method.detailed, 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), - ('single_borehole_short', Method.detailed, 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), - ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), + ('single_borehole', 'detailed', 'uniform_segments', np.array([5.6057331, 6.37369288, 6.61659795])), + ('single_borehole_short', 'detailed', 'uniform_segments', np.array([4.16941861, 4.99989722, 5.29557193])), + ('ten_boreholes_rectangular', 'detailed', 'uniform_segments', np.array([10.96118694, 17.24496533, 19.2536638])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole', Method.detailed, 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), - ('single_borehole_short', Method.detailed, 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), - ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), + ('single_borehole', 'detailed', 'unequal_segments_approx', np.array([5.59717101, 6.36259907, 6.6050007])), + ('single_borehole_short', 'detailed', 'unequal_segments_approx', np.array([4.15784584, 4.98478735, 5.27961509])), + ('ten_boreholes_rectangular', 'detailed', 'unequal_segments_approx', np.array([10.89852244, 17.09793569, 19.07814962])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole', Method.detailed, 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), - ('single_borehole_short', Method.detailed, 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), - ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), + ('single_borehole', 'detailed', 'uniform_segments_approx', np.array([5.60572735, 6.37371464, 6.61642409])), + ('single_borehole_short', 'detailed', 'uniform_segments_approx', np.array([4.16941691, 4.99990922, 5.29542863])), + ('ten_boreholes_rectangular', 'detailed', 'uniform_segments_approx', np.array([10.96035847, 17.24419784, 19.25220421])), ]) def test_gfunctions_UBWT(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -69,7 +68,7 @@ def test_gfunctions_UBWT(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.h for b in boreholes]) + H_mean = np.mean([b.H for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -86,53 +85,53 @@ def test_gfunctions_UBWT(field, method, opts, expected, request): # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole', Method.equivalent, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.equivalent, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'equivalent', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'equivalent', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'equivalent' solver - uniform segments - ('single_borehole', Method.equivalent, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.equivalent, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'equivalent', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'equivalent', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole', Method.equivalent, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.equivalent, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), + ('single_borehole', 'equivalent', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'equivalent', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'equivalent', 'unequal_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole', Method.equivalent, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.equivalent, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), + ('single_borehole', 'equivalent', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'equivalent', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'equivalent', 'uniform_segments_approx', np.array([11.27831426, 18.48076919, 21.00650885])), # 'similarities' solver - unequal segments - ('single_borehole', Method.similarities, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.similarities, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'similarities', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'similarities', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'similarities', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'similarities' solver - uniform segments - ('single_borehole', Method.similarities, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.similarities, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'similarities', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'similarities', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'similarities', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole', Method.similarities, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.similarities, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.similarities, 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', 'similarities', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'similarities', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'similarities', 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole', Method.similarities, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.similarities, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.similarities, 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', 'similarities', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'similarities', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'similarities', 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'detailed' solver - unequal segments - ('single_borehole', Method.detailed, 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.detailed, 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'detailed', 'unequal_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'detailed', 'unequal_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'detailed', 'unequal_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'detailed' solver - uniform segments - ('single_borehole', Method.detailed, 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), - ('single_borehole_short', Method.detailed, 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), - ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), + ('single_borehole', 'detailed', 'uniform_segments', np.array([5.61855789, 6.41336758, 6.66933682])), + ('single_borehole_short', 'detailed', 'uniform_segments', np.array([4.18276733, 5.03671562, 5.34369772])), + ('ten_boreholes_rectangular', 'detailed', 'uniform_segments', np.array([11.27831804, 18.48075762, 21.00669237])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole', Method.detailed, 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.detailed, 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.detailed, 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', 'detailed', 'unequal_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'detailed', 'unequal_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'detailed', 'unequal_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole', Method.detailed, 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), - ('single_borehole_short', Method.detailed, 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), - ('ten_boreholes_rectangular', Method.detailed, 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), + ('single_borehole', 'detailed', 'uniform_segments_approx', np.array([5.61855411, 6.41337915, 6.66915329])), + ('single_borehole_short', 'detailed', 'uniform_segments_approx', np.array([4.18276637, 5.03673008, 5.34353657])), + ('ten_boreholes_rectangular', 'detailed', 'uniform_segments_approx', np.array([11.27751418, 18.47964006, 21.00475366])), ]) def test_gfunctions_UHTR(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -140,7 +139,7 @@ def test_gfunctions_UHTR(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.h for b in boreholes]) + H_mean = np.mean([b.H for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -158,61 +157,61 @@ def test_gfunctions_UHTR(field, method, opts, expected, request): # The 'equivalent' solver is not applied to series-connected boreholes. @pytest.mark.parametrize("field, method, opts, expected", [ # 'equivalent' solver - unequal segments - ('single_borehole_network', Method.equivalent, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', Method.equivalent, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', Method.equivalent, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('single_borehole_network', 'equivalent', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', 'equivalent', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', 'equivalent', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), # 'equivalent' solver - uniform segments - ('single_borehole_network', Method.equivalent, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', Method.equivalent, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', Method.equivalent, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('single_borehole_network', 'equivalent', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', 'equivalent', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', 'equivalent', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), # 'equivalent' solver - unequal segments, FLS approximation - ('single_borehole_network', Method.equivalent, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', Method.equivalent, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', Method.equivalent, 'unequal_segments_approx', np.array([12.66228879, 18.57863253, 20.33526092])), + ('single_borehole_network', 'equivalent', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', 'equivalent', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', 'equivalent', 'unequal_segments_approx', np.array([12.66228879, 18.57863253, 20.33526092])), # 'equivalent' solver - uniform segments, FLS approximation - ('single_borehole_network', Method.equivalent, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', Method.equivalent, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', Method.equivalent, 'uniform_segments_approx', np.array([12.93153466, 18.88937176, 20.63801163])), + ('single_borehole_network', 'equivalent', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', 'equivalent', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', 'equivalent', 'uniform_segments_approx', np.array([12.93153466, 18.88937176, 20.63801163])), # 'similarities' solver - unequal segments - ('single_borehole_network', Method.similarities, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', Method.similarities, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', Method.similarities, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), - ('ten_boreholes_network_rectangular_series', Method.similarities, 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), + ('single_borehole_network', 'similarities', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', 'similarities', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', 'similarities', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('ten_boreholes_network_rectangular_series', 'similarities', 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), # 'similarities' solver - uniform segments - ('single_borehole_network', Method.similarities, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', Method.similarities, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', Method.similarities, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), - ('ten_boreholes_network_rectangular_series', Method.similarities, 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), + ('single_borehole_network', 'similarities', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', 'similarities', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', 'similarities', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('ten_boreholes_network_rectangular_series', 'similarities', 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), # 'similarities' solver - unequal segments, FLS approximation - ('single_borehole_network', Method.similarities, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', Method.similarities, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', Method.similarities, 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), - ('ten_boreholes_network_rectangular_series', Method.similarities, 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), + ('single_borehole_network', 'similarities', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', 'similarities', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', 'similarities', 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), + ('ten_boreholes_network_rectangular_series', 'similarities', 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), # 'similarities' solver - uniform segments, FLS approximation - ('single_borehole_network', Method.similarities, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', Method.similarities, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', Method.similarities, 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), - ('ten_boreholes_network_rectangular_series', Method.similarities, 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), + ('single_borehole_network', 'similarities', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', 'similarities', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', 'similarities', 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), + ('ten_boreholes_network_rectangular_series', 'similarities', 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), # 'detailed' solver - unequal segments - ('single_borehole_network', Method.detailed, 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), - ('single_borehole_network_short', Method.detailed, 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), - ('ten_boreholes_network_rectangular', Method.detailed, 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), - ('ten_boreholes_network_rectangular_series', Method.detailed, 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), + ('single_borehole_network', 'detailed', 'unequal_segments', np.array([5.76597302, 6.51058473, 6.73746895])), + ('single_borehole_network_short', 'detailed', 'unequal_segments', np.array([4.17105954, 5.00930075, 5.30832133])), + ('ten_boreholes_network_rectangular', 'detailed', 'unequal_segments', np.array([12.66229998, 18.57852681, 20.33535907])), + ('ten_boreholes_network_rectangular_series', 'detailed', 'unequal_segments', np.array([3.19050169, 8.8595362, 10.84379419])), # 'detailed' solver - uniform segments - ('single_borehole_network', Method.detailed, 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), - ('single_borehole_network_short', Method.detailed, 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), - ('ten_boreholes_network_rectangular', Method.detailed, 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), - ('ten_boreholes_network_rectangular_series', Method.detailed, 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), + ('single_borehole_network', 'detailed', 'uniform_segments', np.array([5.78644676, 6.5311583, 6.75699875])), + ('single_borehole_network_short', 'detailed', 'uniform_segments', np.array([4.17553236, 5.01476781, 5.31381287])), + ('ten_boreholes_network_rectangular', 'detailed', 'uniform_segments', np.array([12.931553, 18.8892892, 20.63810364])), + ('ten_boreholes_network_rectangular_series', 'detailed', 'uniform_segments', np.array([3.22186337, 8.92628494, 10.91644607])), # 'detailed' solver - unequal segments, FLS approximation - ('single_borehole_network', Method.detailed, 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), - ('single_borehole_network_short', Method.detailed, 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), - ('ten_boreholes_network_rectangular', Method.detailed, 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), - ('ten_boreholes_network_rectangular_series', Method.detailed, 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), + ('single_borehole_network', 'detailed', 'unequal_segments_approx', np.array([5.76596769, 6.51061169, 6.73731276])), + ('single_borehole_network_short', 'detailed', 'unequal_segments_approx', np.array([4.17105984, 5.00931374, 5.30816983])), + ('ten_boreholes_network_rectangular', 'detailed', 'unequal_segments_approx', np.array([12.66136057, 18.57792276, 20.33429034])), + ('ten_boreholes_network_rectangular_series', 'detailed', 'unequal_segments_approx', np.array([3.19002567, 8.85783554, 10.84222402])), # 'detailed' solver - uniform segments, FLS approximation - ('single_borehole_network', Method.detailed, 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), - ('single_borehole_network_short', Method.detailed, 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), - ('ten_boreholes_network_rectangular', Method.detailed, 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), - ('ten_boreholes_network_rectangular_series', Method.detailed, 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), + ('single_borehole_network', 'detailed', 'uniform_segments_approx', np.array([5.78644007, 6.53117706, 6.75684456])), + ('single_borehole_network_short', 'detailed', 'uniform_segments_approx', np.array([4.17553118, 5.01478084, 5.31366109])), + ('ten_boreholes_network_rectangular', 'detailed', 'uniform_segments_approx', np.array([12.93064329, 18.88844718, 20.63710493])), + ('ten_boreholes_network_rectangular_series', 'detailed', 'uniform_segments_approx', np.array([3.22139028, 8.92448715, 10.91485947])), ]) def test_gfunctions_MIFT(field, method, opts, expected, request): # Extract the bore field from the fixture @@ -220,7 +219,7 @@ def test_gfunctions_MIFT(field, method, opts, expected, request): # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.h for b in network.b]) + H_mean = np.mean([b.H for b in network.b]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) @@ -240,21 +239,21 @@ def test_gfunctions_MIFT(field, method, opts, expected, request): # unequal/uniform segments, and with/without the FLS approximation @pytest.mark.parametrize("method, opts, expected", [ # 'similarities' solver - unequal segments - (Method.similarities, 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), + ('similarities', 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), # 'similarities' solver - uniform segments - (Method.similarities, 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), + ('similarities', 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), # 'similarities' solver - unequal segments, FLS approximation - (Method.similarities, 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), + ('similarities', 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), # 'similarities' solver - uniform segments, FLS approximation - (Method.similarities, 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), + ('similarities', 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), # 'detailed' solver - unequal segments - (Method.detailed, 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), + ('detailed', 'unequal_segments', np.array([5.67249989, 6.72866814, 7.15134705])), # 'detailed' solver - uniform segments - (Method.detailed, 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), + ('detailed', 'uniform_segments', np.array([5.68324619, 6.74356205, 7.16738741])), # 'detailed' solver - unequal segments, FLS approximation - (Method.detailed, 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), + ('detailed', 'unequal_segments_approx', np.array([5.66984803, 6.72564218, 7.14826009])), # 'detailed' solver - uniform segments, FLS approximation - (Method.detailed, 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), + ('detailed', 'uniform_segments_approx', np.array([5.67916493, 6.7395222 , 7.16339216])), ]) def test_gfunctions_UBWT(two_boreholes_inclined, method, opts, expected, request): # Extract the bore field from the fixture @@ -262,7 +261,7 @@ def test_gfunctions_UBWT(two_boreholes_inclined, method, opts, expected, request # Extract the g-function options from the fixture options = request.getfixturevalue(opts) # Mean borehole length [m] - H_mean = np.mean([b.h for b in boreholes]) + H_mean = np.mean([b.H for b in boreholes]) alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] ts = H_mean**2 / (9 * alpha) diff --git a/tests/heat_transfer_test.py b/tests/heat_transfer_test.py index 2dbc73e4..bc0a263a 100644 --- a/tests/heat_transfer_test.py +++ b/tests/heat_transfer_test.py @@ -43,7 +43,7 @@ def test_finite_line_source_vertical_to_vertical_single_time_step( b1 = request.getfixturevalue(borehole1)[0] b2 = request.getfixturevalue(borehole2)[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts # Evaluate FLS @@ -85,7 +85,7 @@ def test_finite_line_source_vertical_to_vertical_multiple_time_steps( b1 = request.getfixturevalue(borehole1)[0] b2 = request.getfixturevalue(borehole2)[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] # Times for FLS calculation [s] time = np.array([0.1, 1., 10.]) * ts # Evaluate FLS @@ -162,7 +162,7 @@ def test_finite_line_source_multiple_vertical_boreholes_single_time_step( boreholes = three_boreholes_unequal alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) + ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) # Time for FLS calculation [s] time = ts # Evaluate FLS @@ -248,7 +248,7 @@ def test_finite_line_source_multiple_vertical_boreholes_multiple_time_steps( boreholes = three_boreholes_unequal alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) + ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) # Times for FLS calculation [s] time = np.array([0.1, 1., 10.]) * ts # Evaluate FLS @@ -333,7 +333,7 @@ def test_finite_line_source_Lazzarotto( b2 = gt.boreholes.Borehole( 20., 25., 0.075, 0., 5., tilt=np.pi/15, orientation=4*np.pi/3) alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts * tts # Evaluate FLS @@ -406,7 +406,7 @@ def test_finite_line_source_inclined_to_self( # Extract borehole from fixtures b1 = single_borehole_inclined[0] alpha = 1e-6 # Ground thermal diffusivity [m2/s] - ts = b1.h ** 2 / (9 * alpha) # Borehole characteristic time [s] + ts = b1.H**2 / (9 * alpha) # Borehole characteristic time [s] # Time for FLS calculation [s] time = ts * tts # Evaluate FLS @@ -517,7 +517,7 @@ def test_finite_line_source_multiple_inclined_to_multiple_inclined( boreholes = two_boreholes_inclined alpha = 1e-6 # Ground thermal diffusivity [m2/s] # Bore field characteristic time [s] - ts = np.mean([b.h for b in boreholes]) ** 2 / (9 * alpha) + ts = np.mean([b.H for b in boreholes])**2 / (9 * alpha) # Times for FLS calculation [s] time = ts * tts # Evaluate FLS diff --git a/tests/media_test.py b/tests/media_test.py new file mode 100644 index 00000000..e4d05e7e --- /dev/null +++ b/tests/media_test.py @@ -0,0 +1,213 @@ +""" +Test suite for media module. +""" +import pytest + +import numpy as np + +import pygfunction as gt + +# ============================================================================= +# Test media.Fluid.density +# ============================================================================= +# Test values for various fluid types +@pytest.mark.parametrize("fluid_str, percent, temperature, expected", [ + ('Water', 0, 10, 999.7024701877426), + ('Water', 0, 20, 998.2071504679284), + ('Water', 0, 30, 995.6494539376417), + ('MPG', 10, 10, 1008.2689961144661), + ('MPG', 20, 10, 1017.8626755236311), + ('MPG', 30, 10, 1028.0353104757783), + ('MPG', 10, 20, 1006.2031746231479), + ('MPG', 20, 20, 1014.7765850357973), + ('MPG', 30, 20, 1023.7849656966808), + ('MPG', 10, 30, 1003.2180687671566), + ('MPG', 20, 30, 1010.9146187811484), + ('MPG', 30, 30, 1018.8901487312302), + ('MEG', 10, 10, 1012.5172712114797), + ('MEG', 20, 10, 1026.9421864291571), + ('MEG', 30, 10, 1041.81268177996), + ('MEG', 10, 20, 1010.5788992741457), + ('MEG', 20, 20, 1024.103841343761), + ('MEG', 30, 20, 1038.0455069991867), + ('MEG', 10, 30, 1007.8419592934212), + ('MEG', 20, 30, 1020.5803239949098), + ('MEG', 30, 30, 1033.6985710696938), + ('MMA', 10, 10, 983.4613095855294), + ('MMA', 20, 10, 970.003514932995), + ('MMA', 30, 10, 956.0807785917889), + ('MMA', 10, 20, 981.5369754807216), + ('MMA', 20, 20, 966.7850160911171), + ('MMA', 30, 20, 951.4780716051396), + ('MMA', 10, 30, 978.6950827803122), + ('MMA', 20, 30, 962.8970357199569), + ('MMA', 30, 30, 946.4163175698473), + ('MEA', 10, 10, 984.0169755411894), + ('MEA', 20, 10, 972.8211726834397), + ('MEA', 30, 10, 959.7917807305605), + ('MEA', 10, 20, 981.8412262139618), + ('MEA', 20, 20, 968.916073316228), + ('MEA', 30, 20, 954.0145656811119), + ('MEA', 10, 30, 978.6675214044719), + ('MEA', 20, 30, 964.1970327636251), + ('MEA', 30, 30, 947.7162326339673), + ]) +def test_media_density(fluid_str, percent, temperature, expected): + f = gt.media.Fluid(fluid_str, percent, temperature).fluid + val = f.density(temperature) + print(val) + assert np.isclose(val, expected, rtol=1e-03) + + +# ============================================================================= +# Test media.Fluid.dynamic_viscosity +# ============================================================================= +# Test values for various fluid types +@pytest.mark.parametrize("fluid_str, percent, temperature, expected", [ + ('Water', 0, 10, 0.0013058996603510726), + ('Water', 0, 20, 0.0010015961431205946), + ('Water', 0, 30, 0.0007972217998101543), + ('MPG', 10, 10, 0.0019355497519401303), + ('MPG', 20, 10, 0.0028747158905815936), + ('MPG', 30, 10, 0.00443842147028475), + ('MPG', 10, 20, 0.0014322654827054425), + ('MPG', 20, 20, 0.002030081218434552), + ('MPG', 30, 20, 0.0029649755163276424), + ('MPG', 10, 30, 0.0011051906819817174), + ('MPG', 20, 30, 0.0015077789197685406), + ('MPG', 30, 30, 0.002104117057953085), + ('MEG', 10, 10, 0.0016939732838861141), + ('MEG', 20, 10, 0.00225040111095906), + ('MEG', 30, 10, 0.002982996578124414), + ('MEG', 10, 20, 0.001274521897582636), + ('MEG', 20, 20, 0.001662420238542368), + ('MEG', 30, 20, 0.00216644950875951), + ('MEG', 10, 30, 0.0009944915473250838), + ('MEG', 20, 30, 0.0012758690228681718), + ('MEG', 30, 30, 0.0016388861877625733), + ('MMA', 10, 10, 0.0017708773222801532), + ('MMA', 20, 10, 0.0022114629278959376), + ('MMA', 30, 10, 0.0024870362888294874), + ('MMA', 10, 20, 0.001307290166958664), + ('MMA', 20, 20, 0.001597900499247394), + ('MMA', 30, 20, 0.0017888114211552148), + ('MMA', 10, 30, 0.00100825619191877), + ('MMA', 20, 30, 0.0012062505542303364), + ('MMA', 30, 30, 0.001339885184111998), + ('MEA', 10, 10, 0.0021531435266206062), + ('MEA', 20, 10, 0.0032236607038786053), + ('MEA', 30, 10, 0.004052953292713285), + ('MEA', 10, 20, 0.0015286945963484458), + ('MEA', 20, 20, 0.0021647586328195655), + ('MEA', 30, 20, 0.0026599348716473225), + ('MEA', 10, 30, 0.0011513482333395681), + ('MEA', 20, 30, 0.0015533858907404473), + ('MEA', 30, 30, 0.0018617799374801788), + ]) +def test_media_dynamic_viscosity(fluid_str, percent, temperature, expected): + f = gt.media.Fluid(fluid_str, percent, temperature).fluid + val = f.viscosity(temperature) + print(val) + assert np.isclose(val, expected, rtol=1e-03) + +# ============================================================================= +# Test media.Fluid.specific_heat +# ============================================================================= +# Test values for various fluid types +@pytest.mark.parametrize("fluid_str, percent, temperature, expected", [ + ('Water', 0, 10, 4195.1588859665535), + ('Water', 0, 20, 4184.050924523541), + ('Water', 0, 30, 4179.819671974329), + ('MPG', 10, 10, 4066.567327841638), + ('MPG', 20, 10, 3956.0750111945817), + ('MPG', 30, 10, 3829.9067044799335), + ('MPG', 10, 20, 4076.3409907783143), + ('MPG', 20, 20, 3976.7687703765587), + ('MPG', 30, 20, 3857.0040104952327), + ('MPG', 10, 30, 4087.9816766536514), + ('MPG', 20, 30, 3997.8176072927586), + ('MPG', 30, 30, 3883.8644060855163), + ('MEG', 10, 10, 4040.5254141929463), + ('MEG', 20, 10, 3878.2960125648947), + ('MEG', 30, 10, 3688.510077929425), + ('MEG', 10, 20, 4045.8587434905658), + ('MEG', 20, 20, 3896.1986036794215), + ('MEG', 30, 20, 3718.2510136895853), + ('MEG', 10, 30, 4053.0708838851647), + ('MEG', 20, 30, 3914.3988378361287), + ('MEG', 30, 30, 3747.227085473126), + ('MMA', 10, 10, 4219.150837041468), + ('MMA', 20, 10, 4095.282694548098), + ('MMA', 30, 10, 3938.189018136434), + ('MMA', 10, 20, 4197.520573446068), + ('MMA', 20, 20, 4110.081977854491), + ('MMA', 30, 20, 3986.47248195434), + ('MMA', 10, 30, 4177.092917070355), + ('MMA', 20, 30, 4111.367048690323), + ('MMA', 30, 30, 4012.7465102530355), + ('MEA', 10, 10, 4350.572772963351), + ('MEA', 20, 10, 4348.84164200469), + ('MEA', 30, 10, 4192.58929019259), + ('MEA', 10, 20, 4303.290975720076), + ('MEA', 20, 20, 4328.658186319545), + ('MEA', 30, 20, 4216.106493272698), + ('MEA', 10, 30, 4272.1999935948015), + ('MEA', 20, 30, 4316.492002673067), + ('MEA', 30, 30, 4237.636742030536), + ]) +def test_media_specific_heat(fluid_str, percent, temperature, expected): + f = gt.media.Fluid(fluid_str, percent, temperature).fluid + val = f.specific_heat(temperature) + print(val) + assert np.isclose(val, expected, rtol=1e-03) + +# ============================================================================= +# Test media.Fluid.conductivity +# ============================================================================= +# Test values for various fluid types +@pytest.mark.parametrize("fluid_str, percent, temperature, expected", [ + ('Water', 0, 10, 0.5787774010063071), + ('Water', 0, 20, 0.598012355523438), + ('Water', 0, 30, 0.6143922004176029), + ('MPG', 10, 10, 0.5300549191212082), + ('MPG', 20, 10, 0.4817771638791716), + ('MPG', 30, 10, 0.4364029024368229), + ('MPG', 10, 20, 0.5438139911581278), + ('MPG', 20, 20, 0.4922134827182714), + ('MPG', 30, 20, 0.4444288290269426), + ('MPG', 10, 30, 0.5567347918108109), + ('MPG', 20, 30, 0.5023926225745958), + ('MPG', 30, 30, 0.45249493969037613), + ('MEG', 10, 10, 0.5386105756160928), + ('MEG', 20, 10, 0.496163218175526), + ('MEG', 30, 10, 0.4555075172982103), + ('MEG', 10, 20, 0.5528914770740265), + ('MEG', 20, 20, 0.5076648341960951), + ('MEG', 30, 20, 0.46489722365425923), + ('MEG', 10, 30, 0.5661284239440396), + ('MEG', 20, 30, 0.5186388785688518), + ('MEG', 30, 30, 0.4740758190744439), + ('MMA', 10, 10, 0.524298461828551), + ('MMA', 20, 10, 0.472828484961337), + ('MMA', 30, 10, 0.4245836511047557), + ('MMA', 10, 20, 0.5384009760509022), + ('MMA', 20, 20, 0.48338652493545536), + ('MMA', 30, 20, 0.43210259145515517), + ('MMA', 10, 30, 0.5517880546574834), + ('MMA', 20, 30, 0.49377897672944504), + ('MMA', 30, 30, 0.43976400834470714), + ('MEA', 10, 10, 0.515765397272651), + ('MEA', 20, 10, 0.4570562191793063), + ('MEA', 30, 10, 0.4041064846449754), + ('MEA', 10, 20, 0.5285242624873144), + ('MEA', 20, 20, 0.466309950353855), + ('MEA', 30, 20, 0.41070731333976634), + ('MEA', 10, 30, 0.5408859303860871), + ('MEA', 20, 30, 0.47546296088655077), + ('MEA', 30, 30, 0.4172233323372221), + ]) +def test_media_conductivity(fluid_str, percent, temperature, expected): + f = gt.media.Fluid(fluid_str, percent, temperature).fluid + val = f.conductivity(temperature) + print(val) + assert np.isclose(val, expected, rtol=5e-03) diff --git a/tox.ini b/tox.ini index 117a3594..f9b41526 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.24.5 -envlist = py37, py38, py39 +envlist = py37, py38, py39, py310, py311 isolated_build = true [gh-actions] @@ -8,11 +8,14 @@ python = 3.7: py37 3.8: py38 3.9: py39 + 3.10: py310 + 3.11: py311 [testenv] setenv = PYTHONPATH = {toxinidir} deps = + -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_dev.txt commands = pytest --basetemp={envtmpdir} From ad790cd0ddd8929e3b224cd2684661f83c054be2 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:43:07 +0100 Subject: [PATCH 06/11] Update utilities.py --- pygfunction/utilities.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 7acd2f68..3c743452 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -22,7 +22,34 @@ def cardinal_point(direction): return compass[direction] -def erf_int(x): +sqrt_pi = 1 / np.sqrt(np.pi) + + +def erf_int(x: np.ndarray): + """ + Integral of the error function. + + Parameters + ---------- + x : float or array + Argument. + + Returns + ------- + float or array + Integral of the error function. + + """ + + abs_x = np.abs(x) + y_new = abs_x-sqrt_pi + idx = abs_x < 4 + abs_2 = abs_x[idx] + y_new[idx] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi + return y_new + + +def erf_int_old(x: np.ndarray): """ Integral of the error function. @@ -37,7 +64,7 @@ def erf_int(x): Integral of the error function. """ - return x * erf(x) - 1.0 / np.sqrt(np.pi) * (1.0 - np.exp(-x**2)) + return x * erf(x) - (1.0 - np.exp(-x*x)) / np.sqrt(np.pi) def exp1(x): From 31abb40b32c017446e52ac282f69c6f43634c584 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:46:56 +0100 Subject: [PATCH 07/11] Update utilities.py --- pygfunction/utilities.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 3c743452..fab4dba3 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -4,6 +4,8 @@ import numpy.polynomial.polynomial as poly from scipy.special import erf import warnings +from typing import Union +from numpy.typing import NDArray def cardinal_point(direction): @@ -25,7 +27,7 @@ def cardinal_point(direction): sqrt_pi = 1 / np.sqrt(np.pi) -def erf_int(x: np.ndarray): +def erf_int(x: Union[NDArray[np.float64], float]) -> NDArray[np.float64]: """ Integral of the error function. @@ -49,7 +51,7 @@ def erf_int(x: np.ndarray): return y_new -def erf_int_old(x: np.ndarray): +def erf_int_old(x: Union[NDArray[np.float64], float]) -> NDArray[np.float64]: """ Integral of the error function. From 5fecaf7b15d1617c3f7ddecd9600ae36e695bb5a Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:49:15 +0100 Subject: [PATCH 08/11] Update utilities.py --- pygfunction/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index fab4dba3..f6018025 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -42,7 +42,7 @@ def erf_int(x: Union[NDArray[np.float64], float]) -> NDArray[np.float64]: Integral of the error function. """ - + return erf_int_old(x) abs_x = np.abs(x) y_new = abs_x-sqrt_pi idx = abs_x < 4 From 5ccb20ababa1952311a40f391205b1eaccc19128 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:53:09 +0100 Subject: [PATCH 09/11] Update utilities.py --- pygfunction/utilities.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index f6018025..45cb6665 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -42,7 +42,6 @@ def erf_int(x: Union[NDArray[np.float64], float]) -> NDArray[np.float64]: Integral of the error function. """ - return erf_int_old(x) abs_x = np.abs(x) y_new = abs_x-sqrt_pi idx = abs_x < 4 From 757fdc578123d2180f741df187b6f5047df582a0 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:17:29 +0100 Subject: [PATCH 10/11] Update test_erf_int.py --- tests/test_erf_int.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_erf_int.py b/tests/test_erf_int.py index 59becfc9..9826feef 100644 --- a/tests/test_erf_int.py +++ b/tests/test_erf_int.py @@ -1,6 +1,5 @@ from pygfunction.utilities import erf_int, erf_int_old import numpy as np -from scipy.special import erf from time import perf_counter_ns From 498d676478d5700dbefb9c6bab14ea80f8e7570b Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Mon, 14 Nov 2022 08:33:12 +0100 Subject: [PATCH 11/11] np.less --- pygfunction/utilities.py | 2 +- pyproject.toml | 2 +- tests/test_erf_int.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index 45cb6665..e3f98554 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -44,7 +44,7 @@ def erf_int(x: Union[NDArray[np.float64], float]) -> NDArray[np.float64]: """ abs_x = np.abs(x) y_new = abs_x-sqrt_pi - idx = abs_x < 4 + idx = np.less(abs_x, 4) abs_2 = abs_x[idx] y_new[idx] = abs_2 * erf(abs_2) - (1.0 - np.exp(-abs_2*abs_2)) * sqrt_pi return y_new diff --git a/pyproject.toml b/pyproject.toml index 0a9589bb..32d1323a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -addopts = "--cov=pygfunction" +# addopts = "--cov=pygfunction" testpaths = [ "tests", ] diff --git a/tests/test_erf_int.py b/tests/test_erf_int.py index 9826feef..8b393c88 100644 --- a/tests/test_erf_int.py +++ b/tests/test_erf_int.py @@ -14,7 +14,7 @@ def test_erf_int_error(): y = erf_int_old(x) toc = perf_counter_ns() dt_old1 = toc - tic - assert np.allclose(y, y_new, rtol=0.000_000_01) + assert np.allclose(y, y_new, rtol=1e-10) print(f'new time {dt_new1 / 1_000_000} ms; old time { dt_old1 / 1_000_000} ms') @@ -27,7 +27,7 @@ def test_erf_int_error(): y = erf_int_old(x) toc = perf_counter_ns() dt_old2 = toc - tic - assert np.allclose(y, y_new, rtol=0.000_000_01) + assert np.allclose(y, y_new, rtol=1e-10) print(f'new time {dt_new2 / 1_000_000} ms; old time {dt_old2 / 1_000_000} ms') @@ -40,6 +40,6 @@ def test_erf_int_error(): y = erf_int_old(x) toc = perf_counter_ns() dt_old3 = toc - tic - assert np.allclose(y, y_new, rtol=0.000_000_01) + assert np.allclose(y, y_new, rtol=1e-10) print(f'new time {dt_new3/1_000_000} ms; old time {dt_old3/1_000_000} ms')