Skip to content

Commit 147938f

Browse files
Merge pull request #320 from MassimoCimmino/issue319_solversModule
Issue319 solvers module
2 parents 106fa7a + 5e53299 commit 147938f

File tree

11 files changed

+3175
-3070
lines changed

11 files changed

+3175
-3070
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# History of changes
22

3+
## Version 2.4 (in development)
4+
5+
### Other changes
6+
7+
* [Issue 319](https://github.com/MassimoCimmino/pygfunction/issues/319) - Created `solvers` module. `Solver` classes are moved out of the `gfunction` module and into the new module.
8+
39
## Version 2.3 (2025-04-29)
410

511
### New features

doc/source/modules.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ Modules
1414
modules/media
1515
modules/networks
1616
modules/pipes
17+
modules/solvers
1718
modules/utilities

doc/source/modules/solvers.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. solvers:
2+
3+
**************
4+
Solvers Module
5+
**************
6+
7+
.. automodule:: pygfunction.solvers
8+
:members:
9+
:show-inheritance:

pygfunction/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
media,
88
networks,
99
pipes,
10+
solvers,
1011
utilities,
1112
)

pygfunction/gfunction.py

Lines changed: 15 additions & 3069 deletions
Large diffs are not rendered by default.

pygfunction/solvers/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ._base_solver import _BaseSolver
2+
from .detailed import Detailed
3+
from .equivalent import Equivalent
4+
from .similarities import Similarities
5+
6+
__all__ = [
7+
'_BaseSolver',
8+
'Detailed',
9+
'Equivalent',
10+
'Similarities'
11+
]

pygfunction/solvers/_base_solver.py

Lines changed: 645 additions & 0 deletions
Large diffs are not rendered by default.

pygfunction/solvers/detailed.py

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# -*- coding: utf-8 -*-
2+
from time import perf_counter
3+
4+
import numpy as np
5+
from scipy.interpolate import interp1d as interp1d
6+
7+
from ._base_solver import _BaseSolver
8+
from ..heat_transfer import (
9+
finite_line_source,
10+
finite_line_source_inclined_vectorized,
11+
finite_line_source_vectorized
12+
)
13+
14+
15+
class Detailed(_BaseSolver):
16+
"""
17+
Detailed solver for the evaluation of the g-function.
18+
19+
This solver superimposes the finite line source (FLS) solution to
20+
estimate the g-function of a geothermal bore field. Each borehole is
21+
modeled as a series of finite line source segments, as proposed in
22+
[#Detailed-CimBer2014]_.
23+
24+
Parameters
25+
----------
26+
boreholes : list of Borehole objects
27+
List of boreholes included in the bore field.
28+
network : network object
29+
Model of the network.
30+
time : float or array
31+
Values of time (in seconds) for which the g-function is evaluated.
32+
boundary_condition : str
33+
Boundary condition for the evaluation of the g-function. Should be one
34+
of
35+
36+
- 'UHTR' :
37+
**Uniform heat transfer rate**. This is corresponds to boundary
38+
condition *BC-I* as defined by Cimmino and Bernier (2014)
39+
[#Detailed-CimBer2014]_.
40+
- 'UBWT' :
41+
**Uniform borehole wall temperature**. This is corresponds to
42+
boundary condition *BC-III* as defined by Cimmino and Bernier
43+
(2014) [#Detailed-CimBer2014]_.
44+
- 'MIFT' :
45+
**Mixed inlet fluid temperatures**. This boundary condition was
46+
introduced by Cimmino (2015) [#Detailed-Cimmin2015]_ for
47+
parallel-connected boreholes and extended to mixed
48+
configurations by Cimmino (2019) [#Detailed-Cimmin2019]_.
49+
50+
nSegments : int or list, optional
51+
Number of line segments used per borehole, or list of number of
52+
line segments used for each borehole.
53+
Default is 8.
54+
segment_ratios : array, list of arrays, or callable, optional
55+
Ratio of the borehole length represented by each segment. The
56+
sum of ratios must be equal to 1. The shape of the array is of
57+
(nSegments,) or list of (nSegments[i],). If segment_ratios==None,
58+
segments of equal lengths are considered. If a callable is provided, it
59+
must return an array of size (nSegments,) when provided with nSegments
60+
(of type int) as an argument, or an array of size (nSegments[i],) when
61+
provided with an element of nSegments (of type list).
62+
Default is :func:`utilities.segment_ratios`.
63+
m_flow_borehole : (nInlets,) array or (nMassFlow, nInlets,) array, optional
64+
Fluid mass flow rate into each circuit of the network. If a
65+
(nMassFlow, nInlets,) array is supplied, the
66+
(nMassFlow, nMassFlow,) variable mass flow rate g-functions
67+
will be evaluated using the method of Cimmino (2024)
68+
[#Detailed-Cimmin2024]_. Only required for the 'MIFT' boundary
69+
condition. Only one of 'm_flow_borehole' and 'm_flow_network' can be
70+
provided.
71+
Default is None.
72+
m_flow_network : float or (nMassFlow,) array, optional
73+
Fluid mass flow rate into the network of boreholes. If an array
74+
is supplied, the (nMassFlow, nMassFlow,) variable mass flow
75+
rate g-functions will be evaluated using the method of Cimmino
76+
(2024) [#Detailed-Cimmin2024]_. Only required for the 'MIFT' boundary
77+
condition. Only one of 'm_flow_borehole' and 'm_flow_network' can be
78+
provided.
79+
Default is None.
80+
cp_f : float, optional
81+
Fluid specific isobaric heat capacity (in J/kg.degC). Only required
82+
for the 'MIFT' boundary condition.
83+
Default is None.
84+
approximate_FLS : bool, optional
85+
Set to true to use the approximation of the FLS solution of Cimmino
86+
(2021) [#Detailed-Cimmin2021]_. This approximation does not require the
87+
numerical evaluation of any integral.
88+
Default is False.
89+
nFLS : int, optional
90+
Number of terms in the approximation of the FLS solution. This
91+
parameter is unused if `approximate_FLS` is set to False.
92+
Default is 10. Maximum is 25.
93+
mQuad : int, optional
94+
Number of Gauss-Legendre sample points for the integral over :math:`u`
95+
in the inclined FLS solution.
96+
Default is 11.
97+
linear_threshold : float, optional
98+
Threshold time (in seconds) under which the g-function is
99+
linearized. The g-function value is then interpolated between 0
100+
and its value at the threshold. If linear_threshold==None, the
101+
g-function is linearized for times
102+
`t < r_b**2 / (25 * self.alpha)`.
103+
Default is None.
104+
disp : bool, optional
105+
Set to true to print progression messages.
106+
Default is False.
107+
profiles : bool, optional
108+
Set to true to keep in memory the temperatures and heat extraction
109+
rates.
110+
Default is False.
111+
kind : string, optional
112+
Interpolation method used for segment-to-segment thermal response
113+
factors. See documentation for scipy.interpolate.interp1d.
114+
Default is 'linear'.
115+
dtype : numpy dtype, optional
116+
numpy data type used for matrices and vectors. Should be one of
117+
numpy.single or numpy.double.
118+
Default is numpy.double.
119+
120+
References
121+
----------
122+
.. [#Detailed-CimBer2014] Cimmino, M., & Bernier, M. (2014). A
123+
semi-analytical method to generate g-functions for geothermal bore
124+
fields. International Journal of Heat and Mass Transfer, 70, 641-650.
125+
.. [#Detailed-Cimmin2015] Cimmino, M. (2015). The effects of borehole
126+
thermal resistances and fluid flow rate on the g-functions of geothermal
127+
bore fields. International Journal of Heat and Mass Transfer, 91,
128+
1119-1127.
129+
.. [#Detailed-Cimmin2019] Cimmino, M. (2019). Semi-analytical method for
130+
g-function calculation of bore fields with series- and
131+
parallel-connected boreholes. Science and Technology for the Built
132+
Environment, 25 (8), 1007-1022.
133+
.. [#Detailed-Cimmin2021] Cimmino, M. (2021). An approximation of the
134+
finite line source solution to model thermal interactions between
135+
geothermal boreholes. International Communications in Heat and Mass
136+
Transfer, 127, 105496.
137+
.. [#Detailed-Cimmin2024] Cimmino, M. (2024). g-Functions for fields of
138+
series- and parallel-connected boreholes with variable fluid mass flow
139+
rate and reversible flow direction. Renewable Energy, 228, 120661.
140+
141+
"""
142+
def initialize(self, **kwargs):
143+
"""
144+
Split boreholes into segments.
145+
146+
Returns
147+
-------
148+
nSources : int
149+
Number of finite line heat sources in the borefield used to
150+
initialize the matrix of segment-to-segment thermal response
151+
factors (of size: nSources x nSources).
152+
153+
"""
154+
# Split boreholes into segments
155+
self.boreSegments = self.borehole_segments()
156+
nSources = len(self.boreSegments)
157+
return nSources
158+
159+
def thermal_response_factors(self, time, alpha, kind='linear'):
160+
"""
161+
Evaluate the segment-to-segment thermal response factors for all pairs
162+
of segments in the borefield at all time steps using the finite line
163+
source solution.
164+
165+
This method returns a scipy.interpolate.interp1d object of the matrix
166+
of thermal response factors, containing a copy of the matrix accessible
167+
by h_ij.y[:nSources,:nSources,:nt+1]. The first index along the
168+
third axis corresponds to time t=0. The interp1d object can be used to
169+
obtain thermal response factors at any intermediate time by
170+
h_ij(t)[:nSources,:nSources].
171+
172+
Attributes
173+
----------
174+
time : float or array
175+
Values of time (in seconds) for which the g-function is evaluated.
176+
alpha : float
177+
Soil thermal diffusivity (in m2/s).
178+
kind : string, optional
179+
Interpolation method used for segment-to-segment thermal response
180+
factors. See documentation for scipy.interpolate.interp1d.
181+
Default is 'linear'.
182+
183+
Returns
184+
-------
185+
h_ij : interp1d
186+
interp1d object (scipy.interpolate) of the matrix of
187+
segment-to-segment thermal response factors.
188+
189+
"""
190+
if self.disp:
191+
print('Calculating segment to segment response factors ...',
192+
end='')
193+
# Number of time values
194+
nt = len(np.atleast_1d(time))
195+
# Initialize chrono
196+
tic = perf_counter()
197+
# Initialize segment-to-segment response factors
198+
h_ij = np.zeros((self.nSources, self.nSources, nt+1), dtype=self.dtype)
199+
nBoreholes = len(self.boreholes)
200+
segment_lengths = self.segment_lengths()
201+
202+
# ---------------------------------------------------------------------
203+
# Segment-to-segment thermal response factors for same-borehole
204+
# thermal interactions
205+
# ---------------------------------------------------------------------
206+
h, i_segment, j_segment = \
207+
self._thermal_response_factors_borehole_to_self(time, alpha)
208+
# Broadcast values to h_ij matrix
209+
h_ij[j_segment, i_segment, 1:] = h
210+
# ---------------------------------------------------------------------
211+
# Segment-to-segment thermal response factors for
212+
# borehole-to-borehole thermal interactions
213+
# ---------------------------------------------------------------------
214+
for i, (i0, i1) in enumerate(zip(self._i0Segments, self._i1Segments)):
215+
# Segments of the receiving borehole
216+
b2 = self.boreSegments[i0:i1]
217+
if i+1 < nBoreholes:
218+
# Segments of the emitting borehole
219+
b1 = self.boreSegments[i1:]
220+
h = finite_line_source(
221+
time, alpha, b1, b2, approximation=self.approximate_FLS,
222+
N=self.nFLS, M=self.mQuad)
223+
# Broadcast values to h_ij matrix
224+
h_ij[i0:i1, i1:, 1:] = h
225+
h_ij[i1:, i0:i1, 1:] = \
226+
np.swapaxes(h, 0, 1) * np.divide.outer(
227+
segment_lengths[i0:i1],
228+
segment_lengths[i1:]).T[:,:,np.newaxis]
229+
230+
# Return 2d array if time is a scalar
231+
if np.isscalar(time):
232+
h_ij = h_ij[:,:,1]
233+
234+
# Interp1d object for thermal response factors
235+
h_ij = interp1d(np.hstack((0., time)), h_ij,
236+
kind=kind, copy=True, axis=2)
237+
toc = perf_counter()
238+
if self.disp: print(f' {toc - tic:.3f} sec')
239+
240+
return h_ij
241+
242+
def _thermal_response_factors_borehole_to_self(self, time, alpha):
243+
"""
244+
Evaluate the segment-to-segment thermal response factors for all pairs
245+
of segments between each borehole and itself.
246+
247+
Attributes
248+
----------
249+
time : float or array
250+
Values of time (in seconds) for which the g-function is evaluated.
251+
alpha : float
252+
Soil thermal diffusivity (in m2/s).
253+
254+
Returns
255+
-------
256+
h : array
257+
Finite line source solution.
258+
i_segment : list
259+
Indices of the emitting segments in the bore field.
260+
j_segment : list
261+
Indices of the receiving segments in the bore field.
262+
"""
263+
# Indices of the thermal response factors into h_ij
264+
i_segment = np.concatenate(
265+
[np.repeat(np.arange(i0, i1), nSegments)
266+
for i0, i1, nSegments in zip(
267+
self._i0Segments, self._i1Segments, self.nBoreSegments)
268+
])
269+
j_segment = np.concatenate(
270+
[np.tile(np.arange(i0, i1), nSegments)
271+
for i0, i1, nSegments in zip(
272+
self._i0Segments, self._i1Segments, self.nBoreSegments)
273+
])
274+
# Unpack parameters
275+
x = np.array([b.x for b in self.boreSegments])
276+
y = np.array([b.y for b in self.boreSegments])
277+
H = np.array([b.H for b in self.boreSegments])
278+
D = np.array([b.D for b in self.boreSegments])
279+
r_b = np.array([b.r_b for b in self.boreSegments])
280+
# Distances between boreholes
281+
dis = np.maximum(
282+
np.sqrt((x[i_segment] - x[j_segment])**2 + (y[i_segment] - y[j_segment])**2),
283+
r_b[i_segment])
284+
# FLS solution
285+
if np.all([b.is_vertical() for b in self.boreholes]):
286+
h = finite_line_source_vectorized(
287+
time, alpha,
288+
dis, H[i_segment], D[i_segment], H[j_segment], D[j_segment],
289+
approximation=self.approximate_FLS, N=self.nFLS)
290+
else:
291+
tilt = np.array([b.tilt for b in self.boreSegments])
292+
orientation = np.array([b.orientation for b in self.boreSegments])
293+
h = finite_line_source_inclined_vectorized(
294+
time, alpha,
295+
r_b[i_segment], x[i_segment], y[i_segment], H[i_segment],
296+
D[i_segment], tilt[i_segment], orientation[i_segment],
297+
x[j_segment], y[j_segment], H[j_segment], D[j_segment],
298+
tilt[j_segment], orientation[j_segment], M=self.mQuad,
299+
approximation=self.approximate_FLS, N=self.nFLS)
300+
return h, i_segment, j_segment

0 commit comments

Comments
 (0)