geoloop 0.0.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geoloop/axisym/AxisymetricEL.py +751 -0
- geoloop/axisym/__init__.py +3 -0
- geoloop/bin/Flowdatamain.py +89 -0
- geoloop/bin/Lithologymain.py +84 -0
- geoloop/bin/Loadprofilemain.py +100 -0
- geoloop/bin/Plotmain.py +250 -0
- geoloop/bin/Runbatch.py +81 -0
- geoloop/bin/Runmain.py +86 -0
- geoloop/bin/SingleRunSim.py +928 -0
- geoloop/bin/__init__.py +3 -0
- geoloop/cli/__init__.py +0 -0
- geoloop/cli/batch.py +106 -0
- geoloop/cli/main.py +105 -0
- geoloop/configuration.py +946 -0
- geoloop/constants.py +112 -0
- geoloop/geoloopcore/CoaxialPipe.py +503 -0
- geoloop/geoloopcore/CustomPipe.py +727 -0
- geoloop/geoloopcore/__init__.py +3 -0
- geoloop/geoloopcore/b2g.py +739 -0
- geoloop/geoloopcore/b2g_ana.py +516 -0
- geoloop/geoloopcore/boreholedesign.py +683 -0
- geoloop/geoloopcore/getloaddata.py +112 -0
- geoloop/geoloopcore/pyg_ana.py +280 -0
- geoloop/geoloopcore/pygfield_ana.py +519 -0
- geoloop/geoloopcore/simulationparameters.py +130 -0
- geoloop/geoloopcore/soilproperties.py +152 -0
- geoloop/geoloopcore/strat_interpolator.py +194 -0
- geoloop/lithology/__init__.py +3 -0
- geoloop/lithology/plot_lithology.py +277 -0
- geoloop/lithology/process_lithology.py +695 -0
- geoloop/loadflowdata/__init__.py +3 -0
- geoloop/loadflowdata/flow_data.py +161 -0
- geoloop/loadflowdata/loadprofile.py +325 -0
- geoloop/plotting/__init__.py +3 -0
- geoloop/plotting/create_plots.py +1142 -0
- geoloop/plotting/load_data.py +432 -0
- geoloop/utils/RunManager.py +164 -0
- geoloop/utils/__init__.py +0 -0
- geoloop/utils/helpers.py +841 -0
- geoloop-1.0.0.dist-info/METADATA +120 -0
- geoloop-1.0.0.dist-info/RECORD +46 -0
- geoloop-1.0.0.dist-info/entry_points.txt +2 -0
- geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0.dist-info/licenses/LICENSE.md +2 -1
- geoloop-0.0.1.dist-info/METADATA +0 -10
- geoloop-0.0.1.dist-info/RECORD +0 -6
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/WHEEL +0 -0
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pygfunction as gt
|
|
4
|
+
from matplotlib.ticker import AutoMinorLocator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def thermal_resistance_pipe(r_in: float, r_out: float, k_p: float) -> float:
|
|
8
|
+
"""
|
|
9
|
+
Compute the conductive thermal resistance of a cylindrical pipe wall.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
r_in : float
|
|
14
|
+
Inner radius of the pipe (m).
|
|
15
|
+
r_out : float
|
|
16
|
+
Outer radius of the pipe (m).
|
|
17
|
+
k_p : float
|
|
18
|
+
Thermal conductivity of the pipe material (W/m·K).
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
float
|
|
23
|
+
Thermal resistance of the pipe wall (m·K/W).
|
|
24
|
+
"""
|
|
25
|
+
R_p = np.log(r_out / r_in) / (2 * np.pi * k_p)
|
|
26
|
+
return R_p
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def thermal_resistance_pipe_insulated(
|
|
30
|
+
r_in: float, r_out: float, insu_dr: float, k_p: float, insu_k: float
|
|
31
|
+
) -> float:
|
|
32
|
+
"""
|
|
33
|
+
Compute the conductive thermal resistance of a pipe with an insulated
|
|
34
|
+
middle section of its wall thickness.
|
|
35
|
+
|
|
36
|
+
The wall is divided into:
|
|
37
|
+
- inner pipe material
|
|
38
|
+
- insulated section
|
|
39
|
+
- outer pipe material
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
r_in : float
|
|
44
|
+
Inner radius of the pipe (m).
|
|
45
|
+
r_out : float
|
|
46
|
+
Outer radius of the pipe (m).
|
|
47
|
+
insu_dr : float
|
|
48
|
+
Fraction of the pipe wall thickness that is insulated (0–1).
|
|
49
|
+
k_p : float
|
|
50
|
+
Thermal conductivity of the pipe material (W/m·K).
|
|
51
|
+
insu_k : float
|
|
52
|
+
Thermal conductivity of the insulation (W/m·K).
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
float
|
|
57
|
+
Total radial thermal resistance (m·K/W).
|
|
58
|
+
"""
|
|
59
|
+
# Total wall thickness
|
|
60
|
+
wall_thickness = r_out - r_in
|
|
61
|
+
iso_thickness = wall_thickness * insu_dr
|
|
62
|
+
|
|
63
|
+
# Locate insulation symmetrically in the wall
|
|
64
|
+
r_iso_in = r_in + 0.5 * (wall_thickness - iso_thickness)
|
|
65
|
+
r_iso_out = r_iso_in + iso_thickness
|
|
66
|
+
|
|
67
|
+
# Compute resistances for each region
|
|
68
|
+
R_inner = thermal_resistance_pipe(r_in, r_iso_in, k_p)
|
|
69
|
+
R_iso = thermal_resistance_pipe(r_iso_in, r_iso_out, insu_k)
|
|
70
|
+
R_outer = thermal_resistance_pipe(r_iso_out, r_out, k_p)
|
|
71
|
+
|
|
72
|
+
R_p = R_inner + R_iso + R_outer
|
|
73
|
+
|
|
74
|
+
return R_p
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CustomPipe(gt.pipes._BasePipe):
|
|
78
|
+
"""
|
|
79
|
+
Pipe model with depth-dependent ambient temperatures.
|
|
80
|
+
|
|
81
|
+
Supports U-tubes and multi-U-tubes with N inlet pipes and M outlet pipes.
|
|
82
|
+
Uses pygfunction (Cimmino & Cook, 2022) for thermal resistance networks.
|
|
83
|
+
|
|
84
|
+
Internal resistances are based on the multipole method of
|
|
85
|
+
Claesson & Hellström (2011). Fluid properties are obtained via pygfunction.
|
|
86
|
+
|
|
87
|
+
Contains information regarding the physical dimensions and thermal
|
|
88
|
+
characteristics of the pipes and the grout material, as well as methods to
|
|
89
|
+
evaluate fluid temperatures and heat extraction rates based on the work of
|
|
90
|
+
Hellstrom [#Single-Hellstrom1991]_. Internal borehole thermal resistances
|
|
91
|
+
are evaluated using the multipole method of Claesson and Hellstrom
|
|
92
|
+
[#Single-Claesson2011b]_.
|
|
93
|
+
|
|
94
|
+
The effective borehole thermal resistance is evaluated using the method
|
|
95
|
+
of Cimmino [#Single-Cimmin2019]_. This is valid for any number of pipes.
|
|
96
|
+
|
|
97
|
+
References
|
|
98
|
+
----------
|
|
99
|
+
.. [#Cimmino2022] Cimmino, M., & Cook, J.C. (2022). pygfunction 2.2: New features and improvements in accuracy and computational efficiency.
|
|
100
|
+
In Research Conference Proceedings, IGSHPA Annual Conference 2022 (pp. 45-52).
|
|
101
|
+
International Ground Source Heat Pump Association. DOI: https://doi.org/10.22488/okstate.22.00001
|
|
102
|
+
.. [#Single-Hellstrom1991] Hellstrom, G. (1991). Ground heat storage.
|
|
103
|
+
Thermal Analyses of Duct Storage Systems I: Theory. PhD Thesis.
|
|
104
|
+
University of Lund, Department of Mathematical Physics. Lund, Sweden.
|
|
105
|
+
.. [#Single-Claesson2011b] Claesson, J., & Hellstrom, G. (2011).
|
|
106
|
+
Multipole method to calculate borehole thermal resistances in a borehole
|
|
107
|
+
heat exchanger. HVAC&R Research, 17(6), 895-911.
|
|
108
|
+
.. [#Single-Cimmin2019] Cimmino, M. (2019). Semi-analytical method for
|
|
109
|
+
g-function calculation of bore fields with series- and
|
|
110
|
+
parallel-connected boreholes. Science and Technology for the Built
|
|
111
|
+
Environment, 25 (8), 1007-1022.
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
pos,
|
|
118
|
+
r_in,
|
|
119
|
+
r_out,
|
|
120
|
+
borehole,
|
|
121
|
+
k_g,
|
|
122
|
+
k_p,
|
|
123
|
+
J=3,
|
|
124
|
+
nInlets=1,
|
|
125
|
+
m_flow=1.0,
|
|
126
|
+
T_f=10,
|
|
127
|
+
fluid_str="Water",
|
|
128
|
+
percent=100,
|
|
129
|
+
epsilon=1e-6,
|
|
130
|
+
ncalcsegments=1,
|
|
131
|
+
R_p=[],
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Initialize a custom borehole pipe model and compute its thermal and
|
|
135
|
+
hydraulic properties.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
pos : list of (float, float)
|
|
140
|
+
Pipe coordinates inside the borehole.
|
|
141
|
+
r_in : float or array_like
|
|
142
|
+
Inner radius of the pipes (m).
|
|
143
|
+
r_out : float or array_like
|
|
144
|
+
Outer radius of the pipes (m).
|
|
145
|
+
borehole : gt.boreholes.Borehole
|
|
146
|
+
Borehole geometry object.
|
|
147
|
+
k_g : float
|
|
148
|
+
Grout thermal conductivity (W/m·K).
|
|
149
|
+
k_p : float
|
|
150
|
+
Pipe thermal conductivity (W/m·K).
|
|
151
|
+
J : int, optional
|
|
152
|
+
Number of multipoles per pipe. Default is 3.
|
|
153
|
+
nInlets : int, optional
|
|
154
|
+
Number of inlet pipes. Default is 1.
|
|
155
|
+
m_flow : float, optional
|
|
156
|
+
Mass flow rate (kg/s). Default is 1.0.
|
|
157
|
+
T_f : float, optional
|
|
158
|
+
Inlet fluid temperature (°C). Default is 10.
|
|
159
|
+
fluid_str : str, optional
|
|
160
|
+
Fluid type. Default is "Water".
|
|
161
|
+
percent : float, optional
|
|
162
|
+
Fluid mixture percentage. Default is 100.
|
|
163
|
+
epsilon : float, optional
|
|
164
|
+
Pipe roughness. Default is 1e-6.
|
|
165
|
+
ncalcsegments : int, optional
|
|
166
|
+
Number of segments for thermal resistance evaluation.
|
|
167
|
+
R_p : list or array, optional
|
|
168
|
+
Precomputed pipe thermal resistances.
|
|
169
|
+
|
|
170
|
+
Attributes
|
|
171
|
+
----------
|
|
172
|
+
R_p : list of float
|
|
173
|
+
Pipe thermal resistances (m·K/W).
|
|
174
|
+
h_f : ndarray
|
|
175
|
+
Convective heat transfer coefficient per pipe.
|
|
176
|
+
Rd : ndarray
|
|
177
|
+
Δ-circuit thermal resistance per segment.
|
|
178
|
+
R : ndarray
|
|
179
|
+
Thermal resistance matrix.
|
|
180
|
+
R1 : ndarray
|
|
181
|
+
Inverse thermal resistance matrix.
|
|
182
|
+
m_flow_pipe : ndarray
|
|
183
|
+
Mass flow per pipe.
|
|
184
|
+
|
|
185
|
+
Notes
|
|
186
|
+
-----
|
|
187
|
+
The expected array shapes of input parameters and outputs are documented
|
|
188
|
+
for each class method. `nInlets` and `nOutlets` are the number of inlets
|
|
189
|
+
and outlets to the borehole, and both are equal to 1 for a single U-tube
|
|
190
|
+
borehole. `nSegments` is the number of discretized segments along the
|
|
191
|
+
borehole. `nPipes` is the number of pipes (i.e. the number of U-tubes) in
|
|
192
|
+
the borehole, equal to 1. `nDepths` is the number of depths at which
|
|
193
|
+
temperatures are evaluated.
|
|
194
|
+
"""
|
|
195
|
+
self.pos = pos
|
|
196
|
+
self.nPipes = len(pos)
|
|
197
|
+
|
|
198
|
+
# convert to arrays if needed
|
|
199
|
+
if np.isscalar(r_in):
|
|
200
|
+
r_in = r_in * np.ones(self.nPipes)
|
|
201
|
+
self.r_in = r_in
|
|
202
|
+
if np.isscalar(r_out):
|
|
203
|
+
r_out = r_out * np.ones(self.nPipes)
|
|
204
|
+
|
|
205
|
+
self.r_out = r_out
|
|
206
|
+
self.b = borehole
|
|
207
|
+
self.k_s = 1.0
|
|
208
|
+
self.k_g = k_g
|
|
209
|
+
self.k_p = k_p
|
|
210
|
+
|
|
211
|
+
self.J = J
|
|
212
|
+
self.nInlets = nInlets
|
|
213
|
+
self.nOutlets = self.nPipes - self.nInlets
|
|
214
|
+
self.ncalcsegments = ncalcsegments
|
|
215
|
+
|
|
216
|
+
# Pipe thermal resistances
|
|
217
|
+
if len(R_p) == 0:
|
|
218
|
+
rp = thermal_resistance_pipe(r_in, r_out, k_p)
|
|
219
|
+
self.R_p = []
|
|
220
|
+
for i in range(ncalcsegments):
|
|
221
|
+
self.R_p.append(rp)
|
|
222
|
+
else:
|
|
223
|
+
self.R_p = R_p
|
|
224
|
+
|
|
225
|
+
# Initialize flow rate and fluid properties including fluid resistisity with pipes
|
|
226
|
+
self.m_flow = m_flow
|
|
227
|
+
self.m_flow_pipe = m_flow * np.ones(self.nPipes)
|
|
228
|
+
self.m_flow_pipe[: self.nInlets] = m_flow / self.nInlets
|
|
229
|
+
self.m_flow_pipe[self.nInlets :] = -m_flow / self.nOutlets
|
|
230
|
+
|
|
231
|
+
# fluid
|
|
232
|
+
fluid = gt.media.Fluid(fluid_str, percent, T=T_f)
|
|
233
|
+
|
|
234
|
+
self.cp_f = fluid.cp # Fluid specific isobaric heat capacity (J/kg.K)
|
|
235
|
+
self.rho_f = fluid.rho # Fluid density (kg/m3)
|
|
236
|
+
self.mu_f = fluid.mu # Fluid dynamic viscosity (kg/m.s)
|
|
237
|
+
self.k_f = fluid.k # Fluid thermal conductivity (W/m.K)
|
|
238
|
+
|
|
239
|
+
self.epsilon = epsilon
|
|
240
|
+
|
|
241
|
+
self.h_f = np.zeros(self.nPipes)
|
|
242
|
+
self.Rd = []
|
|
243
|
+
|
|
244
|
+
# initalise flow scaling
|
|
245
|
+
self.update_scaleflow(1.0)
|
|
246
|
+
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def k_g(self) -> float:
|
|
251
|
+
return self._k_g
|
|
252
|
+
|
|
253
|
+
@k_g.setter
|
|
254
|
+
def k_g(self, value: float) -> None:
|
|
255
|
+
self._k_g = value
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def R(self) -> np.ndarray:
|
|
259
|
+
return self._R
|
|
260
|
+
|
|
261
|
+
@R.setter
|
|
262
|
+
def R(self, value: np.ndarray) -> None:
|
|
263
|
+
self._R = value
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def Rd(self) -> np.ndarray:
|
|
267
|
+
return self._Rd
|
|
268
|
+
|
|
269
|
+
@Rd.setter
|
|
270
|
+
def Rd(self, value: np.ndarray) -> None:
|
|
271
|
+
self._Rd = value
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def ncalcsegments(self) -> int:
|
|
275
|
+
return self._ncalcsegments
|
|
276
|
+
|
|
277
|
+
@ncalcsegments.setter
|
|
278
|
+
def ncalcsegments(self, value: int) -> None:
|
|
279
|
+
self._ncalcsegments = value
|
|
280
|
+
|
|
281
|
+
def create_multi_u_tube(self):
|
|
282
|
+
"""
|
|
283
|
+
Build a standard pygfunction U-tube / multi-U-tube object
|
|
284
|
+
using depth-independent pipe properties.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
SingleUTube or MultipleUTube
|
|
289
|
+
pygfunction pipe object.
|
|
290
|
+
"""
|
|
291
|
+
pos = self.pos
|
|
292
|
+
rp_in = self.r_in
|
|
293
|
+
rp_out = self.r_out
|
|
294
|
+
borehole = self.b
|
|
295
|
+
|
|
296
|
+
k_s = np.average(self.k_s)
|
|
297
|
+
k_g = np.average(self.k_g)
|
|
298
|
+
h_f = self.h_f[0]
|
|
299
|
+
|
|
300
|
+
R_f_ser = 1.0 / (h_f * 2 * np.pi * rp_in)
|
|
301
|
+
R_p = self.R_p
|
|
302
|
+
# uniform pipe and fluid resisitivty
|
|
303
|
+
Rfp = R_f_ser[0] + R_p[0][0]
|
|
304
|
+
|
|
305
|
+
if len(rp_in) == 2:
|
|
306
|
+
single_u_tube = gt.pipes.SingleUTube(
|
|
307
|
+
pos, rp_in[0], rp_out[0], borehole, k_s, k_g, Rfp
|
|
308
|
+
)
|
|
309
|
+
single_u_tube.h_f = h_f
|
|
310
|
+
return single_u_tube
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
utube = gt.pipes.MultipleUTube(
|
|
314
|
+
pos,
|
|
315
|
+
rp_in[0],
|
|
316
|
+
rp_out[0],
|
|
317
|
+
borehole,
|
|
318
|
+
k_s,
|
|
319
|
+
k_g,
|
|
320
|
+
Rfp,
|
|
321
|
+
nPipes=self.nInlets,
|
|
322
|
+
config="parallel",
|
|
323
|
+
)
|
|
324
|
+
utube.h_f = h_f
|
|
325
|
+
return utube
|
|
326
|
+
|
|
327
|
+
def update_scaleflow(self, scaleflow: float = 1.0) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Update the flow scaling factor and recalculate convective and thermal
|
|
330
|
+
resistances.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
scaleflow : float, optional
|
|
335
|
+
Scaling multiplier applied to the mass flow rate. Default is 1.0.
|
|
336
|
+
|
|
337
|
+
Notes
|
|
338
|
+
-----
|
|
339
|
+
Assumes that `k_g` and `k_s` are arrays of length `ncalcsegments`,
|
|
340
|
+
allowing depth-dependent thermal properties.
|
|
341
|
+
"""
|
|
342
|
+
self.scaleflow = scaleflow
|
|
343
|
+
|
|
344
|
+
hfnew = np.ones(self.nPipes)
|
|
345
|
+
for i in range(self.nPipes):
|
|
346
|
+
hfnew[i] = gt.pipes.convective_heat_transfer_coefficient_circular_pipe(
|
|
347
|
+
abs(self.m_flow_pipe[i] * self.scaleflow),
|
|
348
|
+
self.r_in[i],
|
|
349
|
+
self.mu_f,
|
|
350
|
+
self.rho_f,
|
|
351
|
+
self.k_f,
|
|
352
|
+
self.cp_f,
|
|
353
|
+
self.epsilon,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
hfdif = np.subtract(hfnew, self.h_f)
|
|
357
|
+
hfdot = np.dot(hfdif, hfdif)
|
|
358
|
+
|
|
359
|
+
# Skip update if small change and sizes match
|
|
360
|
+
if (self.ncalcsegments == len(self.Rd)) and (hfdot < 1):
|
|
361
|
+
return
|
|
362
|
+
else:
|
|
363
|
+
self.h_f = hfnew
|
|
364
|
+
|
|
365
|
+
self.R_f = 1.0 / (self.h_f * 2 * np.pi * self.r_in)
|
|
366
|
+
|
|
367
|
+
# Delta-circuit thermal resistances
|
|
368
|
+
self.update_thermal_resistances()
|
|
369
|
+
|
|
370
|
+
def init_thermal_resistances(self, k_g: np.ndarray, R_p: list[np.ndarray]) -> None:
|
|
371
|
+
"""
|
|
372
|
+
Initialize depth-dependent thermal resistances using provided
|
|
373
|
+
conductivity and pipe resistance arrays.
|
|
374
|
+
|
|
375
|
+
This routine is called from the B2G.runsimulation method, in order to generate thermal resistances based
|
|
376
|
+
on actual segments determined by len(k_g) and len(R_p)
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
k_g : array_like
|
|
381
|
+
Grout thermal conductivity for each depth segment.
|
|
382
|
+
R_p : list of array_like
|
|
383
|
+
Pipe thermal resistance values for each segment.
|
|
384
|
+
"""
|
|
385
|
+
self.ncalcsegments = len(k_g)
|
|
386
|
+
self.k_g = k_g
|
|
387
|
+
self.R_p = R_p
|
|
388
|
+
|
|
389
|
+
self.update_scaleflow(1.0)
|
|
390
|
+
|
|
391
|
+
# Precompute inverses of resistance matrices
|
|
392
|
+
self.R1 = []
|
|
393
|
+
for i in range(self.ncalcsegments):
|
|
394
|
+
R1 = np.linalg.inv(self.R[i])
|
|
395
|
+
self.R1.append(R1)
|
|
396
|
+
|
|
397
|
+
def update_thermal_resistances(self, initialize_stored_coeff: bool = False) -> None:
|
|
398
|
+
"""
|
|
399
|
+
Update the delta-circuit of thermal resistances.
|
|
400
|
+
|
|
401
|
+
This methods updates the values of the delta-circuit thermal
|
|
402
|
+
resistances based on the provided fluid to outer pipe wall thermal
|
|
403
|
+
resistance.
|
|
404
|
+
|
|
405
|
+
Its dimension corresponds to ncalcsegments (which is dictated by b2g.py (nx nodes)
|
|
406
|
+
or b2g_ana (nsegments) and takes into account depth dependent effects of insulation
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
initialize_stored_coeff : bool, optional
|
|
411
|
+
If True, also reinitialize stored coefficients. Default is False.
|
|
412
|
+
"""
|
|
413
|
+
self.R = []
|
|
414
|
+
self.Rd = []
|
|
415
|
+
|
|
416
|
+
for k in range(self.ncalcsegments):
|
|
417
|
+
R_fp = self.R_f + self.R_p[k]
|
|
418
|
+
|
|
419
|
+
# Delta-circuit thermal resistances
|
|
420
|
+
(R, Rd) = gt.pipes.thermal_resistances(
|
|
421
|
+
self.pos, self.r_out, self.b.r_b, self.k_s, self.k_g[k], R_fp, J=self.J
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
self.R.append(R)
|
|
425
|
+
self.Rd.append(Rd)
|
|
426
|
+
|
|
427
|
+
if initialize_stored_coeff:
|
|
428
|
+
self._initialize_stored_coefficients()
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
def get_Rs(self, nyear: float, alpha: float = 1e-6) -> float:
|
|
432
|
+
"""
|
|
433
|
+
Approximate long-term borehole resistance Rs using an analytical estimate.
|
|
434
|
+
Calculate Rs for simple approximation for Tb-T0 = Rs qc
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
nyear : float
|
|
439
|
+
Duration (years) used to compute the effective thermal resistance.
|
|
440
|
+
alpha : float, optional
|
|
441
|
+
Soil thermal diffusivity (m²/s). Default is 1e-6.
|
|
442
|
+
|
|
443
|
+
Returns
|
|
444
|
+
-------
|
|
445
|
+
float
|
|
446
|
+
Approximate long-term borehole resistance Rs (m·K/W).
|
|
447
|
+
"""
|
|
448
|
+
r_b = self.b.r_b
|
|
449
|
+
ts = nyear * 3600 * 24 * 365.25
|
|
450
|
+
|
|
451
|
+
Rs = (np.log((4 * alpha * ts) / (r_b**2)) - np.euler_gamma) / (
|
|
452
|
+
4 * np.pi * self.k_s
|
|
453
|
+
)
|
|
454
|
+
return Rs
|
|
455
|
+
|
|
456
|
+
def get_temperature_depthvar(
|
|
457
|
+
self,
|
|
458
|
+
T_f_in: float,
|
|
459
|
+
signpower: float | np.ndarray,
|
|
460
|
+
Rs: np.ndarray,
|
|
461
|
+
soil_props,
|
|
462
|
+
nsegments: int = 10,
|
|
463
|
+
) -> tuple:
|
|
464
|
+
"""
|
|
465
|
+
Compute fully depth-dependent inlet/outlet fluid temperatures, borehole
|
|
466
|
+
wall temperatures, pipe temperatures, and heat extraction along the borehole.
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
T_f_in : float
|
|
471
|
+
Inlet fluid temperature (°C).
|
|
472
|
+
signpower : float
|
|
473
|
+
Sign of the thermal load direction (±1).
|
|
474
|
+
Rs : array_like
|
|
475
|
+
Long-term borehole resistance values for each segment, based on approximation for Tb-T0 = Rs qc.
|
|
476
|
+
soil_props : SoilProperties
|
|
477
|
+
Soil properties object.
|
|
478
|
+
nsegments : int, optional
|
|
479
|
+
Number of depth segments. Default is 10.
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
tuple
|
|
484
|
+
(T_f_out, power, Reff, pipe_temps, borehole_temps, segment_heat_flows)
|
|
485
|
+
"""
|
|
486
|
+
bh = self.b
|
|
487
|
+
|
|
488
|
+
hstep = bh.H / nsegments
|
|
489
|
+
zmin = bh.D + 0.5 * hstep
|
|
490
|
+
zmax = bh.D + bh.H - 0.5 * hstep
|
|
491
|
+
zseg = np.linspace(zmin, zmax, nsegments)
|
|
492
|
+
|
|
493
|
+
Tg_borehole = soil_props.getTg(zseg)
|
|
494
|
+
qbz = Tg_borehole * 0.0
|
|
495
|
+
|
|
496
|
+
R1 = np.copy(self._R)
|
|
497
|
+
b = np.arange(nsegments, dtype=float)
|
|
498
|
+
|
|
499
|
+
for i in range(nsegments):
|
|
500
|
+
R1[i] = np.linalg.inv(self._R[i])
|
|
501
|
+
b[i] = np.sum(R1[i] @ np.ones(self.nPipes))
|
|
502
|
+
|
|
503
|
+
# storage arrays
|
|
504
|
+
ptemp = np.arange((nsegments + 1) * self.nPipes, dtype=float).reshape(
|
|
505
|
+
nsegments + 1, self.nPipes
|
|
506
|
+
)
|
|
507
|
+
Tb = np.arange(nsegments, dtype=float)
|
|
508
|
+
Tf = Tb * 0.0
|
|
509
|
+
|
|
510
|
+
# initialize the top temperatures (inlet)
|
|
511
|
+
ptemp[0, : self.nInlets] = T_f_in
|
|
512
|
+
|
|
513
|
+
# iterate for T_f_out such that bottom temperatures become the same
|
|
514
|
+
dtfout = 0
|
|
515
|
+
dtempold = 0
|
|
516
|
+
dtempnew = 1
|
|
517
|
+
T_f_out = T_f_in + 1e-1 * signpower
|
|
518
|
+
iterate = True
|
|
519
|
+
|
|
520
|
+
while abs(dtempnew) > 1e-4 or iterate:
|
|
521
|
+
# set outlet boundary condition guess
|
|
522
|
+
ptemp[0, self.nInlets :] = T_f_out
|
|
523
|
+
|
|
524
|
+
# loop over each segment
|
|
525
|
+
for i in range(nsegments):
|
|
526
|
+
# Tf is only for output purposes
|
|
527
|
+
Tf[i] = np.average(ptemp[i])
|
|
528
|
+
|
|
529
|
+
# prep next segment's temperatures
|
|
530
|
+
ptemp[i + 1] = ptemp[i]
|
|
531
|
+
|
|
532
|
+
# iteration variables within the segment
|
|
533
|
+
dtemp = 1
|
|
534
|
+
tseg = 0.5 * (ptemp[i] + ptemp[i + 1])
|
|
535
|
+
|
|
536
|
+
# check that Rs[i]*b is not close to 1 if so modify with small number
|
|
537
|
+
if Rs[i] < 0:
|
|
538
|
+
Rs[i] = abs(Rs[i])
|
|
539
|
+
|
|
540
|
+
# iterate until pipe temperatures converge
|
|
541
|
+
while np.dot(dtemp, dtemp) > 1e-10:
|
|
542
|
+
tsegold = tseg
|
|
543
|
+
|
|
544
|
+
a = np.sum(R1[i] @ tseg)
|
|
545
|
+
Tb[i] = (Rs[i] * a + Tg_borehole[i]) / (1 + Rs[i] * b[i])
|
|
546
|
+
|
|
547
|
+
q = R1[i] @ (tseg - Tb[i])
|
|
548
|
+
|
|
549
|
+
ptemp[i + 1] = ptemp[i] - q * hstep / (
|
|
550
|
+
self.m_flow_pipe * self.scaleflow * self.cp_f
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
tseg = 0.5 * (ptemp[i] + ptemp[i + 1])
|
|
554
|
+
dtemp = tseg - tsegold
|
|
555
|
+
|
|
556
|
+
qbz[i] = -np.sum(q) * hstep
|
|
557
|
+
|
|
558
|
+
dtempnew = np.sum(
|
|
559
|
+
ptemp[nsegments, self.nInlets :] * self.m_flow_pipe[self.nInlets :]
|
|
560
|
+
) + np.sum(
|
|
561
|
+
ptemp[nsegments, : self.nInlets] * self.m_flow_pipe[: self.nInlets]
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# Update outlet temperature guess
|
|
565
|
+
if abs(dtfout) > 0:
|
|
566
|
+
g = dtfout / (dtempnew - dtempold)
|
|
567
|
+
dtfout = -dtempnew * g
|
|
568
|
+
iterate = False
|
|
569
|
+
else:
|
|
570
|
+
dtfout = 1e-1
|
|
571
|
+
iterate = True
|
|
572
|
+
|
|
573
|
+
dtempold = dtempnew
|
|
574
|
+
T_f_out += dtfout
|
|
575
|
+
|
|
576
|
+
# Final power and effective resistance
|
|
577
|
+
power = (T_f_out - T_f_in) * self.m_flow * self.scaleflow * self.cp_f
|
|
578
|
+
|
|
579
|
+
if abs(np.sum(qbz) - power) > 1:
|
|
580
|
+
print("power is not sumq (sumq, power):", np.sum(qbz), ",", power)
|
|
581
|
+
|
|
582
|
+
if abs(power) > 1e-3:
|
|
583
|
+
Reff = -np.average(Tf - Tb) * bh.H / power
|
|
584
|
+
else:
|
|
585
|
+
Reff = -1
|
|
586
|
+
|
|
587
|
+
return T_f_out, power, Reff, ptemp, Tb, qbz
|
|
588
|
+
|
|
589
|
+
def get_temperature_depthvar_power(
|
|
590
|
+
self, power: float | np.ndarray, Rs: np.ndarray, soil_props, nsegments: int = 10
|
|
591
|
+
) -> tuple:
|
|
592
|
+
"""
|
|
593
|
+
Compute inlet and outlet temperatures to satisfy a prescribed power extraction.
|
|
594
|
+
|
|
595
|
+
Parameters
|
|
596
|
+
----------
|
|
597
|
+
power : float
|
|
598
|
+
Target borehole heat extraction rate (W).
|
|
599
|
+
Rs : array_like
|
|
600
|
+
Long-term borehole resistance per segment, based on approximation for Tb-T0 = Rs qc.
|
|
601
|
+
soil_props : SoilProperties
|
|
602
|
+
Soil properties object.
|
|
603
|
+
nsegments : int, optional
|
|
604
|
+
Number of depth segments. Default is 10.
|
|
605
|
+
|
|
606
|
+
Returns
|
|
607
|
+
-------
|
|
608
|
+
tuple
|
|
609
|
+
(T_f_out, T_f_in, Reff, pipe_temps, Tb, qbz)
|
|
610
|
+
"""
|
|
611
|
+
bh = self.b
|
|
612
|
+
|
|
613
|
+
# Initial guess for inlet temperature (assume same as soil at borehole top)
|
|
614
|
+
T_f_in = soil_props.getTg(bh.D)
|
|
615
|
+
|
|
616
|
+
# Initialize iteration variables
|
|
617
|
+
dpowerold = 0
|
|
618
|
+
dpowernew = 1
|
|
619
|
+
dtfin = 0
|
|
620
|
+
|
|
621
|
+
# Iteratively adjust inlet temperature to meet target power
|
|
622
|
+
iterate = True
|
|
623
|
+
while abs(dpowernew) > 1e-3 or iterate:
|
|
624
|
+
(T_f_out, newpower, Reff, ptemp, Tb, qbz) = self.get_temperature_depthvar(
|
|
625
|
+
T_f_in, np.sign(power), Rs, soil_props, nsegments=nsegments
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# Compute difference between computed and target power
|
|
629
|
+
dpowernew = newpower - power
|
|
630
|
+
|
|
631
|
+
if abs(dtfin) > 0:
|
|
632
|
+
# Use previous iteration to accelerate convergence (gain factor)
|
|
633
|
+
gain = dtfin / (dpowernew - dpowerold)
|
|
634
|
+
dtfin = -dpowernew * gain
|
|
635
|
+
iterate = False
|
|
636
|
+
else:
|
|
637
|
+
# If first iteration, use small fixed step
|
|
638
|
+
dtfin = 1e-1
|
|
639
|
+
iterate = True
|
|
640
|
+
|
|
641
|
+
dpowerold = dpowernew
|
|
642
|
+
# Update inlet temperature for next iteration
|
|
643
|
+
T_f_in += dtfin
|
|
644
|
+
|
|
645
|
+
return T_f_out, T_f_in, Reff, ptemp, Tb, qbz
|
|
646
|
+
|
|
647
|
+
def visualize_pipes(self):
|
|
648
|
+
"""
|
|
649
|
+
Plot a cross-sectional diagram of the borehole and pipe layout.
|
|
650
|
+
|
|
651
|
+
Returns
|
|
652
|
+
-------
|
|
653
|
+
matplotlib.figure.Figure
|
|
654
|
+
Figure containing the visualization.
|
|
655
|
+
"""
|
|
656
|
+
plt.rc("font", size=12)
|
|
657
|
+
plt.rc("xtick", labelsize=12)
|
|
658
|
+
plt.rc("ytick", labelsize=12)
|
|
659
|
+
plt.rc("lines", lw=1.5, markersize=5.0)
|
|
660
|
+
plt.rc("savefig", dpi=500)
|
|
661
|
+
|
|
662
|
+
fig = plt.figure()
|
|
663
|
+
ax = fig.add_subplot(111)
|
|
664
|
+
ax.set_xlabel(r"$x$ [m]")
|
|
665
|
+
ax.set_ylabel(r"$y$ [m]")
|
|
666
|
+
ax.axis("equal")
|
|
667
|
+
|
|
668
|
+
# Draw major and minor tick marks inwards
|
|
669
|
+
ax.tick_params(
|
|
670
|
+
axis="both",
|
|
671
|
+
which="both",
|
|
672
|
+
direction="in",
|
|
673
|
+
bottom=True,
|
|
674
|
+
top=True,
|
|
675
|
+
left=True,
|
|
676
|
+
right=True,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Auto-adjust minor tick marks
|
|
680
|
+
ax.xaxis.set_minor_locator(AutoMinorLocator())
|
|
681
|
+
ax.yaxis.set_minor_locator(AutoMinorLocator())
|
|
682
|
+
|
|
683
|
+
# Color cycle
|
|
684
|
+
colors = plt.cm.tab20.colors
|
|
685
|
+
lw = plt.rcParams["lines.linewidth"]
|
|
686
|
+
|
|
687
|
+
# Borehole wall outline
|
|
688
|
+
ax.plot(
|
|
689
|
+
[-self.b.r_b, 0.0, self.b.r_b, 0.0],
|
|
690
|
+
[0.0, self.b.r_b, 0.0, -self.b.r_b],
|
|
691
|
+
"k.",
|
|
692
|
+
alpha=0.0,
|
|
693
|
+
)
|
|
694
|
+
borewall = plt.Circle(
|
|
695
|
+
(0.0, 0.0), radius=self.b.r_b, fill=False, color="k", linestyle="--", lw=lw
|
|
696
|
+
)
|
|
697
|
+
ax.add_patch(borewall)
|
|
698
|
+
|
|
699
|
+
# Pipes
|
|
700
|
+
for i in range(self.nPipes):
|
|
701
|
+
# Coordinates of pipes
|
|
702
|
+
(x_in, y_in) = self.pos[i]
|
|
703
|
+
|
|
704
|
+
# Pipe outline (inlet)
|
|
705
|
+
pipe_in_in = plt.Circle(
|
|
706
|
+
(x_in, y_in),
|
|
707
|
+
radius=self.r_in[i],
|
|
708
|
+
fill=False,
|
|
709
|
+
linestyle="-",
|
|
710
|
+
color=colors[i],
|
|
711
|
+
lw=lw,
|
|
712
|
+
)
|
|
713
|
+
pipe_in_out = plt.Circle(
|
|
714
|
+
(x_in, y_in),
|
|
715
|
+
radius=self.r_out[i],
|
|
716
|
+
fill=False,
|
|
717
|
+
linestyle="-",
|
|
718
|
+
color=colors[i],
|
|
719
|
+
lw=lw,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
ax.text(x_in, y_in, i, ha="center", va="center")
|
|
723
|
+
ax.add_patch(pipe_in_in)
|
|
724
|
+
ax.add_patch(pipe_in_out)
|
|
725
|
+
|
|
726
|
+
plt.tight_layout()
|
|
727
|
+
return fig
|