geoloop 0.0.1__py3-none-any.whl → 1.0.0b1__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 +535 -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 +697 -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 +1137 -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.0b1.dist-info/METADATA +112 -0
- geoloop-1.0.0b1.dist-info/RECORD +46 -0
- geoloop-1.0.0b1.dist-info/entry_points.txt +2 -0
- geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0b1.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.0b1.dist-info}/WHEEL +0 -0
- {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pygfunction as gt
|
|
6
|
+
from pygfunction.media import Fluid
|
|
7
|
+
from scipy import optimize
|
|
8
|
+
|
|
9
|
+
from geoloop.configuration import SingleRunConfig
|
|
10
|
+
from geoloop.geoloopcore.CoaxialPipe import CoaxialPipe
|
|
11
|
+
from geoloop.geoloopcore.CustomPipe import (
|
|
12
|
+
CustomPipe,
|
|
13
|
+
thermal_resistance_pipe,
|
|
14
|
+
thermal_resistance_pipe_insulated,
|
|
15
|
+
)
|
|
16
|
+
from geoloop.geoloopcore.strat_interpolator import StratInterpolator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BoreholeDesign:
|
|
20
|
+
COAXIAL: str = "COAXIAL"
|
|
21
|
+
"""Configuration type for coaxial pipes."""
|
|
22
|
+
|
|
23
|
+
UTUBE: str = "UTUBE"
|
|
24
|
+
"""Configuration type for U-tube pipes."""
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
Representation of a borehole heat exchanger (BHE) design, including
|
|
28
|
+
geometric properties, pipe configuration, and material parameters.
|
|
29
|
+
|
|
30
|
+
The class supports two configurations:
|
|
31
|
+
|
|
32
|
+
**COAXIAL**
|
|
33
|
+
- One inlet (outer pipe) and one outlet (inner pipe).
|
|
34
|
+
- Pipes are concentric and centered at (0, 0).
|
|
35
|
+
- No `pos` specification is needed.
|
|
36
|
+
|
|
37
|
+
**UTUBE**
|
|
38
|
+
- One or more inlet and outlet pipes.
|
|
39
|
+
- Pipes are arranged radially symmetric around the borehole center.
|
|
40
|
+
- Inlets are assumed connected to outlets at the borehole bottom.
|
|
41
|
+
- Radial positions must be constant for all inlets and constant for
|
|
42
|
+
all outlets.
|
|
43
|
+
|
|
44
|
+
Notes
|
|
45
|
+
-----
|
|
46
|
+
- Depth-dependent grout conductivity is supported via interpolation.
|
|
47
|
+
- Optional thermal insulation can be applied to the outlet pipe(s).
|
|
48
|
+
- For PYGFIELD models, additional parameters define field layout and
|
|
49
|
+
borehole inclination.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
type: str,
|
|
55
|
+
H: float,
|
|
56
|
+
D: float,
|
|
57
|
+
r_b: float,
|
|
58
|
+
r_in: list[float],
|
|
59
|
+
r_out: list[float],
|
|
60
|
+
k_p: float | list[float],
|
|
61
|
+
k_g: float | list[float],
|
|
62
|
+
pos: list[list[float]] = [[0.0, 0.0], [0.0, 0.0]],
|
|
63
|
+
J: int = 3,
|
|
64
|
+
nInlets: int = 1,
|
|
65
|
+
m_flow: float = 1.0,
|
|
66
|
+
T_f: float = 10.0,
|
|
67
|
+
fluid_str: str = "Water",
|
|
68
|
+
fluid_percent: float = 100.0,
|
|
69
|
+
epsilon: float = 1e-6,
|
|
70
|
+
z_k_g: list[float] | None = None,
|
|
71
|
+
ncalcsegments: int = 1,
|
|
72
|
+
insu_z: float = 0.0,
|
|
73
|
+
insu_dr: float = 0.0,
|
|
74
|
+
insu_k: float = 0.03,
|
|
75
|
+
N: int = 1,
|
|
76
|
+
M: int = 1,
|
|
77
|
+
R: float = 3.0,
|
|
78
|
+
inclination_start: float = 0.0,
|
|
79
|
+
inclination_end: float = 0.0,
|
|
80
|
+
num_tiltedsegments: int = 1,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Initialize a borehole design with geometry, pipe configuration, and material
|
|
84
|
+
properties.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
type : {"COAXIAL", "UTUBE"}
|
|
89
|
+
Borehole configuration type.
|
|
90
|
+
H : float
|
|
91
|
+
Borehole length (m).
|
|
92
|
+
D : float
|
|
93
|
+
Buried depth of borehole top (m).
|
|
94
|
+
r_b : float
|
|
95
|
+
Borehole radius (m).
|
|
96
|
+
r_in : array_like
|
|
97
|
+
Inner radii of pipe(s) (m).
|
|
98
|
+
r_out : array_like
|
|
99
|
+
Outer radii of pipe(s) (m).
|
|
100
|
+
k_p : float or array_like
|
|
101
|
+
Thermal conductivity of the pipe material (W/m·K).
|
|
102
|
+
k_g : float or array_like
|
|
103
|
+
Grout thermal conductivity. If array-like, depth-dependent values
|
|
104
|
+
must be supplied along with `z_k_g`.
|
|
105
|
+
pos : tuple of float, optional
|
|
106
|
+
Radial position of pipe(s) for UTUBE layouts. Ignored for COAXIAL.
|
|
107
|
+
J : int, optional
|
|
108
|
+
Number of segments used in `pygfunction` internal pipe discretization.
|
|
109
|
+
nInlets : int, optional
|
|
110
|
+
Number of inlet pipes (UTUBE).
|
|
111
|
+
m_flow : float, optional
|
|
112
|
+
Mass flow rate of circulating fluid (kg/s).
|
|
113
|
+
T_f : float, optional
|
|
114
|
+
Initial fluid temperature (°C).
|
|
115
|
+
fluid_str : {"Water", "MPG", "MEG", "MMA", "MEA"}, optional
|
|
116
|
+
Fluid type for hydraulic and thermal properties.
|
|
117
|
+
fluid_percent : float, optional
|
|
118
|
+
Percentage of the working fluid relative to water.
|
|
119
|
+
epsilon : float, optional
|
|
120
|
+
Pipe roughness for hydraulic calculations (m).
|
|
121
|
+
z_k_g : array_like, optional
|
|
122
|
+
Depth breakpoints corresponding to grout thermal conductivities.
|
|
123
|
+
ncalcsegments : int, optional
|
|
124
|
+
Number of vertical discretization segments for thermal calculations.
|
|
125
|
+
insu_z : float, optional
|
|
126
|
+
Depth up to which outlet pipes are insulated (m).
|
|
127
|
+
insu_dr : float, optional
|
|
128
|
+
Fraction of pipe wall thickness that is insulated.
|
|
129
|
+
insu_k : float, optional
|
|
130
|
+
Insulation thermal conductivity (W/m·K).
|
|
131
|
+
N, : int, optional
|
|
132
|
+
Total nr of boreholes in borehole field for PYGFIELD models.
|
|
133
|
+
M : int, optional
|
|
134
|
+
Boreholes per side of the borehole field for PYGFIELD models (only equally sided fields are supported).
|
|
135
|
+
R : float, optional
|
|
136
|
+
Borefield radius or spacing parameter.
|
|
137
|
+
inclination_start : float, optional
|
|
138
|
+
Start angle of borehole inclination (degrees).
|
|
139
|
+
inclination_end : float, optional
|
|
140
|
+
End angle of borehole inclination (degrees).
|
|
141
|
+
num_tiltedsegments : int, optional
|
|
142
|
+
Number of segments for inclined/tilted borehole discretization.
|
|
143
|
+
|
|
144
|
+
Notes
|
|
145
|
+
-----
|
|
146
|
+
- The thermal resistance of insulated pipes is computed in `getR_p`.
|
|
147
|
+
- Depth-dependent grout conductivity is evaluated via `StratInterpolator`.
|
|
148
|
+
- A `CustomPipe` or `CoaxialPipe` object is created automatically.
|
|
149
|
+
"""
|
|
150
|
+
self.D = D
|
|
151
|
+
self.H = H
|
|
152
|
+
self.r_b = r_b
|
|
153
|
+
self.r_in = np.asarray(r_in)
|
|
154
|
+
self.r_out = np.asarray(r_out)
|
|
155
|
+
self.n_p = len(r_out)
|
|
156
|
+
self.nInlets = nInlets
|
|
157
|
+
self.k_p = k_p * np.ones(self.n_p)
|
|
158
|
+
self.pos = np.asarray(pos)
|
|
159
|
+
self.ncalcsegments = ncalcsegments
|
|
160
|
+
|
|
161
|
+
dz = H / ncalcsegments
|
|
162
|
+
zmin = D + 0.5 * dz
|
|
163
|
+
zmax = D + H - 0.5 * dz
|
|
164
|
+
self.zseg = np.linspace(zmin, zmax, ncalcsegments)
|
|
165
|
+
|
|
166
|
+
self.k_g = np.atleast_1d(k_g)
|
|
167
|
+
if z_k_g == None:
|
|
168
|
+
self.z_k_g = np.ones_like(k_g)
|
|
169
|
+
else:
|
|
170
|
+
self.z_k_g = np.asarray(z_k_g)
|
|
171
|
+
|
|
172
|
+
self.interpolatorKg = StratInterpolator(self.z_k_g, self.k_g, stepvalue=True)
|
|
173
|
+
|
|
174
|
+
self.type = type
|
|
175
|
+
self.J = J
|
|
176
|
+
self.m_flow = m_flow
|
|
177
|
+
self.T_f = T_f
|
|
178
|
+
self.fluid_str = fluid_str
|
|
179
|
+
self.fluid_percent = fluid_percent
|
|
180
|
+
self.epsilon = epsilon
|
|
181
|
+
|
|
182
|
+
self.insu_z = insu_z
|
|
183
|
+
self.insu_k = insu_k
|
|
184
|
+
self.insu_dr = insu_dr
|
|
185
|
+
|
|
186
|
+
# (JDvW) I think these can be set to None creating the borehole design object, only created from config with None
|
|
187
|
+
self.Re_in = None
|
|
188
|
+
self.Re_out = None
|
|
189
|
+
|
|
190
|
+
self.N = N
|
|
191
|
+
self.M = M
|
|
192
|
+
self.R = R
|
|
193
|
+
self.inclination_start = inclination_start
|
|
194
|
+
self.inclination_end = inclination_end
|
|
195
|
+
self.num_tiltedsegments = num_tiltedsegments
|
|
196
|
+
|
|
197
|
+
# modify the R_p for the insulated part assume the thickness of the r_in/r_out constant
|
|
198
|
+
|
|
199
|
+
self.customPipe = self.get_custom_pipe()
|
|
200
|
+
|
|
201
|
+
def get_r_p(self, z: np.ndarray) -> list[np.ndarray]:
|
|
202
|
+
"""
|
|
203
|
+
Compute pipe thermal resistance at specific depths.
|
|
204
|
+
|
|
205
|
+
Insulation is applied only to outlet pipes (i.e., pipes with index
|
|
206
|
+
``>= nInlets``) and only for depths shallower than `insu_z`.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
z : array_like of float
|
|
211
|
+
Depth values (m) at which pipe resistances are evaluated.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
list of ndarray
|
|
216
|
+
List containing pipe resistances for each depth.
|
|
217
|
+
Each entry contains an array of size ``n_p``.
|
|
218
|
+
"""
|
|
219
|
+
npipes = len(self.r_out)
|
|
220
|
+
|
|
221
|
+
R_p = []
|
|
222
|
+
for i in range(len(z)):
|
|
223
|
+
rp = thermal_resistance_pipe(self.r_in, self.r_out, self.k_p)
|
|
224
|
+
if (self.insu_z > 0) and (z[i] < self.insu_z):
|
|
225
|
+
for ip in range(self.nInlets, npipes):
|
|
226
|
+
if self.insu_dr > 0:
|
|
227
|
+
rp[ip] = thermal_resistance_pipe_insulated(
|
|
228
|
+
self.r_in[ip],
|
|
229
|
+
self.r_out[ip],
|
|
230
|
+
self.insu_dr,
|
|
231
|
+
self.k_p[ip],
|
|
232
|
+
self.insu_k,
|
|
233
|
+
)
|
|
234
|
+
R_p.append(rp)
|
|
235
|
+
|
|
236
|
+
return R_p
|
|
237
|
+
|
|
238
|
+
def get_k_g(self, zstart: Sequence[float], zend: Sequence[float]) -> np.ndarray:
|
|
239
|
+
"""
|
|
240
|
+
Interpolate grout conductivity for a list of vertical intervals.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
zstart : sequence of float
|
|
245
|
+
Lower bounds of intervals.
|
|
246
|
+
zend : sequence of float
|
|
247
|
+
Upper bounds of intervals.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
np.ndarray
|
|
252
|
+
Grout conductivity per interval.
|
|
253
|
+
"""
|
|
254
|
+
k_g = self.interpolatorKg.interp(zstart, zend)
|
|
255
|
+
|
|
256
|
+
return k_g
|
|
257
|
+
|
|
258
|
+
def get_custom_pipe(self) -> CustomPipe:
|
|
259
|
+
"""
|
|
260
|
+
Construct and return the appropriate pipe model instance (CoaxialPipe or CustomPipe).
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
CoaxialPipe | CustomPipe
|
|
265
|
+
Constructed pipe representation compatible with the rest of the code.
|
|
266
|
+
"""
|
|
267
|
+
# create pygfunction borehole
|
|
268
|
+
self.borehole = gt.boreholes.Borehole(self.H, self.D, self.r_b, x=0.0, y=0.0)
|
|
269
|
+
|
|
270
|
+
# compute grout thermal conductivity with depth
|
|
271
|
+
zz = np.linspace(self.D, self.D + self.H, self.ncalcsegments + 1)
|
|
272
|
+
k_g = self.get_k_g(zz[:-1], zz[1:])
|
|
273
|
+
|
|
274
|
+
# compute pipe resistances
|
|
275
|
+
R_p = self.get_r_p(self.zseg)
|
|
276
|
+
|
|
277
|
+
custom_pipe = None
|
|
278
|
+
if self.type == BoreholeDesign.COAXIAL:
|
|
279
|
+
custom_pipe = CoaxialPipe(
|
|
280
|
+
self.r_in,
|
|
281
|
+
self.r_out,
|
|
282
|
+
self.borehole,
|
|
283
|
+
k_g,
|
|
284
|
+
self.k_p,
|
|
285
|
+
J=self.J,
|
|
286
|
+
m_flow=self.m_flow,
|
|
287
|
+
fluid_str=self.fluid_str,
|
|
288
|
+
percent=self.fluid_percent,
|
|
289
|
+
epsilon=self.epsilon,
|
|
290
|
+
ncalcsegments=self.ncalcsegments,
|
|
291
|
+
R_p=R_p,
|
|
292
|
+
T_f=self.T_f,
|
|
293
|
+
)
|
|
294
|
+
elif self.type == BoreholeDesign.UTUBE:
|
|
295
|
+
custom_pipe = CustomPipe(
|
|
296
|
+
self.pos,
|
|
297
|
+
self.r_in,
|
|
298
|
+
self.r_out,
|
|
299
|
+
self.borehole,
|
|
300
|
+
k_g,
|
|
301
|
+
self.k_p,
|
|
302
|
+
J=self.J,
|
|
303
|
+
nInlets=self.nInlets,
|
|
304
|
+
m_flow=self.m_flow,
|
|
305
|
+
fluid_str=self.fluid_str,
|
|
306
|
+
percent=self.fluid_percent,
|
|
307
|
+
epsilon=self.epsilon,
|
|
308
|
+
ncalcsegments=self.ncalcsegments,
|
|
309
|
+
R_p=R_p,
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
print(
|
|
313
|
+
"error in BoreholeDesign:getCustomPipe type of boreholedesign not recognized ",
|
|
314
|
+
self.type,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return custom_pipe
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_config(cls, config: SingleRunConfig) -> "BoreholeDesign":
|
|
321
|
+
"""
|
|
322
|
+
Create a BoreholeDesign instance from a configuration object.
|
|
323
|
+
|
|
324
|
+
The configuration object should contain keys consistent with the
|
|
325
|
+
`BoreholeDesign` constructor arguments.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
config : SingleRunConfig
|
|
330
|
+
Object containing borehole design parameters.
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
BoreholeDesign
|
|
335
|
+
Configured instance.
|
|
336
|
+
"""
|
|
337
|
+
if config.r_in is None:
|
|
338
|
+
# Example: derive inner radius from r_out using SDR
|
|
339
|
+
r_out = np.asarray(config.r_out)
|
|
340
|
+
if config.SDR:
|
|
341
|
+
config.r_in = r_out - ((2 * r_out) / config.SDR)
|
|
342
|
+
else:
|
|
343
|
+
raise ValueError("r_in missing and no SDR provided.")
|
|
344
|
+
|
|
345
|
+
return cls(
|
|
346
|
+
type=config.type,
|
|
347
|
+
H=config.H,
|
|
348
|
+
D=config.D,
|
|
349
|
+
r_b=config.r_b,
|
|
350
|
+
r_in=config.r_in,
|
|
351
|
+
r_out=config.r_out,
|
|
352
|
+
k_p=config.k_p,
|
|
353
|
+
k_g=config.k_g,
|
|
354
|
+
pos=config.pos,
|
|
355
|
+
nInlets=config.nInlets,
|
|
356
|
+
m_flow=config.m_flow,
|
|
357
|
+
T_f=config.Tin,
|
|
358
|
+
fluid_str=config.fluid_str,
|
|
359
|
+
fluid_percent=config.fluid_percent,
|
|
360
|
+
epsilon=config.epsilon,
|
|
361
|
+
z_k_g=config.z_k_g,
|
|
362
|
+
ncalcsegments=config.nsegments,
|
|
363
|
+
insu_z=config.insu_z,
|
|
364
|
+
insu_dr=config.insu_dr,
|
|
365
|
+
insu_k=config.insu_k,
|
|
366
|
+
N=config.field_N,
|
|
367
|
+
M=config.field_M,
|
|
368
|
+
R=config.field_R,
|
|
369
|
+
inclination_start=config.field_inclination_start,
|
|
370
|
+
inclination_end=config.field_inclination_end,
|
|
371
|
+
num_tiltedsegments=config.field_segments,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def visualize_pipes(self, filename: str | Path) -> None:
|
|
375
|
+
"""
|
|
376
|
+
Vsualize the borehole design, including pipe layout and create a figure.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
filename : str
|
|
381
|
+
Output path for the saved figure.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
None
|
|
386
|
+
|
|
387
|
+
"""
|
|
388
|
+
fig = self.customPipe.visualize_pipes()
|
|
389
|
+
fig.savefig(filename)
|
|
390
|
+
|
|
391
|
+
def fluid_friction_factor(
|
|
392
|
+
self,
|
|
393
|
+
ipipe: int,
|
|
394
|
+
m_flow_pipe: float,
|
|
395
|
+
mu_f: float,
|
|
396
|
+
rho_f: float,
|
|
397
|
+
epsilon: float,
|
|
398
|
+
tol: float = 1e-6,
|
|
399
|
+
) -> tuple[float, float, float]:
|
|
400
|
+
"""
|
|
401
|
+
Evaluate the Darcy-Weisbach friction factor. It calculates the hydraulic diameter D, and the cross_section flow
|
|
402
|
+
area, depending on the pipe configuration.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
m_flow_pipe : float
|
|
407
|
+
Fluid mass flow rate (in kg/s) into the pipe.
|
|
408
|
+
mu_f : float
|
|
409
|
+
Fluid dynamic viscosity (in kg/m-s).
|
|
410
|
+
rho_f : float
|
|
411
|
+
Fluid density (in kg/m3).
|
|
412
|
+
epsilon : float
|
|
413
|
+
Pipe roughness (in meters).
|
|
414
|
+
tol : float
|
|
415
|
+
Relative convergence tolerance on Darcy friction factor.
|
|
416
|
+
Default is 1.0e-6.
|
|
417
|
+
|
|
418
|
+
Returns
|
|
419
|
+
-------
|
|
420
|
+
fDarcy : float
|
|
421
|
+
Darcy friction factor.
|
|
422
|
+
dpdl: float
|
|
423
|
+
Pressure loss
|
|
424
|
+
Re : float
|
|
425
|
+
Reynolds number.
|
|
426
|
+
"""
|
|
427
|
+
if (self.type == BoreholeDesign.UTUBE) or (ipipe == 1):
|
|
428
|
+
# Hydraulic diameter
|
|
429
|
+
D_h = 2.0 * self.r_in[ipipe]
|
|
430
|
+
A_cs = np.pi * self.r_in[ipipe] ** 2
|
|
431
|
+
else:
|
|
432
|
+
# hydraulic parameters for coaxial
|
|
433
|
+
A_cs = np.pi * (self.r_in[0] ** 2 - self.r_out[1] ** 2)
|
|
434
|
+
D_h = 2 * (self.r_in[0] - self.r_out[1])
|
|
435
|
+
|
|
436
|
+
# Relative roughness
|
|
437
|
+
E = epsilon / D_h
|
|
438
|
+
# Fluid velocity
|
|
439
|
+
V_flow = m_flow_pipe / rho_f
|
|
440
|
+
V = V_flow / A_cs
|
|
441
|
+
# Reynolds number
|
|
442
|
+
Re = rho_f * V * D_h / mu_f
|
|
443
|
+
|
|
444
|
+
if Re == 0:
|
|
445
|
+
fDarcy = np.nan
|
|
446
|
+
elif Re < 2.3e3:
|
|
447
|
+
# Darcy friction factor for laminar flow
|
|
448
|
+
fDarcy = 64.0 / Re
|
|
449
|
+
else:
|
|
450
|
+
# Colebrook-White equation for rough pipes
|
|
451
|
+
fDarcy = 0.02
|
|
452
|
+
df = 1.0e99
|
|
453
|
+
while abs(df / fDarcy) > tol:
|
|
454
|
+
one_over_sqrt_f = -2.0 * np.log10(
|
|
455
|
+
E / 3.7 + 2.51 / (Re * np.sqrt(fDarcy))
|
|
456
|
+
)
|
|
457
|
+
fDarcy_new = 1.0 / one_over_sqrt_f**2
|
|
458
|
+
df = fDarcy_new - fDarcy
|
|
459
|
+
fDarcy = fDarcy_new
|
|
460
|
+
|
|
461
|
+
dpdl = fDarcy * 0.5 * rho_f * V * V / D_h
|
|
462
|
+
|
|
463
|
+
return fDarcy, dpdl, Re
|
|
464
|
+
|
|
465
|
+
def dploop_nosyphon(
|
|
466
|
+
self, tempfluid: np.ndarray, flowrate: np.ndarray, effpump: float
|
|
467
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
468
|
+
"""
|
|
469
|
+
Compute loop pressure drop (no syphon) and pumping power.
|
|
470
|
+
|
|
471
|
+
Parameters
|
|
472
|
+
----------
|
|
473
|
+
tempfluid : ndarray
|
|
474
|
+
Fluid temperature time series (°C).
|
|
475
|
+
flowrate : ndarray
|
|
476
|
+
Mass flow rate time series (kg/s).
|
|
477
|
+
effpump : float
|
|
478
|
+
Pump efficiency (0–1).
|
|
479
|
+
|
|
480
|
+
Returns
|
|
481
|
+
-------
|
|
482
|
+
dpsumtime : ndarray
|
|
483
|
+
Pressure drop time series (bar).
|
|
484
|
+
qpump : ndarray
|
|
485
|
+
Power [W] required to drive the pumping
|
|
486
|
+
"""
|
|
487
|
+
qpump = flowrate * 0
|
|
488
|
+
dpsumtime = flowrate * 0
|
|
489
|
+
f = Fluid(self.fluid_str, self.fluid_percent, T=tempfluid)
|
|
490
|
+
|
|
491
|
+
ipipes = [0, self.n_p - 1]
|
|
492
|
+
npipes = [self.nInlets, self.n_p - self.nInlets]
|
|
493
|
+
|
|
494
|
+
Re_in_out = np.ones((len(ipipes), len(flowrate)))
|
|
495
|
+
|
|
496
|
+
for itime in range(len(flowrate)):
|
|
497
|
+
dpsum = 0
|
|
498
|
+
|
|
499
|
+
for ip in range(len(ipipes)):
|
|
500
|
+
ipipe = ipipes[ip] # Index of the current pipe
|
|
501
|
+
mflow = flowrate / npipes[ip] # Distribute flowrate across pipes
|
|
502
|
+
|
|
503
|
+
# Get dynamic viscosity and density for the fluid at the current temperature
|
|
504
|
+
mu_f = f.dynamic_viscosity()
|
|
505
|
+
rho_f = f.density()
|
|
506
|
+
|
|
507
|
+
# Calculate friction factor, pressure drop, and Reynolds number
|
|
508
|
+
fDarcy, dpdl, Re = self.fluid_friction_factor(
|
|
509
|
+
ipipe, mflow[itime], mu_f, rho_f, self.epsilon
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# Sum the pressure drop over the borehole height
|
|
513
|
+
dpsum += dpdl * self.borehole.H
|
|
514
|
+
|
|
515
|
+
# Store the Reynolds nr. for the different pipes over time in one array
|
|
516
|
+
Re_in_out[ip, itime] = Re
|
|
517
|
+
|
|
518
|
+
# Calculate the required pumping power for this time step
|
|
519
|
+
qpump[itime] = dpsum * (flowrate[itime] / rho_f) / effpump
|
|
520
|
+
|
|
521
|
+
# Store the pressure drop at this time step
|
|
522
|
+
dpsumtime[itime] = dpsum
|
|
523
|
+
|
|
524
|
+
# store the reynolds nr. over time for the inlet and outlet pipes separately
|
|
525
|
+
self.Re_in = Re_in_out[0]
|
|
526
|
+
self.Re_out = Re_in_out[1]
|
|
527
|
+
|
|
528
|
+
# Return the total pressure drop (converted to bar) and pumping power (W)
|
|
529
|
+
return dpsumtime / 1e5, qpump
|
|
530
|
+
|
|
531
|
+
def findflowrate_dploop(
|
|
532
|
+
self,
|
|
533
|
+
dplooptarget: float,
|
|
534
|
+
tempfluid: np.ndarray,
|
|
535
|
+
flowrate: float,
|
|
536
|
+
effpump: float,
|
|
537
|
+
) -> float:
|
|
538
|
+
"""
|
|
539
|
+
Calculate flowrate matching with dplooptarget based on root finding algorithm
|
|
540
|
+
(brentq is used with a scaling of the given flowrate, in range 0.01-10).
|
|
541
|
+
|
|
542
|
+
Parameters
|
|
543
|
+
----------
|
|
544
|
+
dplooptarget : float
|
|
545
|
+
Target pumping pressure (bar).
|
|
546
|
+
tempfluid : ndarray
|
|
547
|
+
Fluid temperatures (°C).
|
|
548
|
+
flowrate : float
|
|
549
|
+
Initial flow rate (kg/s) used as scale reference.
|
|
550
|
+
effpump : float
|
|
551
|
+
Pump efficiency.
|
|
552
|
+
|
|
553
|
+
Returns
|
|
554
|
+
-------
|
|
555
|
+
float
|
|
556
|
+
Mass flow rate (kg/s) that results in the desired pumping pressure.
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
sol = optimize.root_scalar(
|
|
560
|
+
self.root_func_dploop,
|
|
561
|
+
args=(dplooptarget, tempfluid, flowrate, effpump),
|
|
562
|
+
bracket=[0.01, 100],
|
|
563
|
+
method="brentq",
|
|
564
|
+
)
|
|
565
|
+
return sol.root
|
|
566
|
+
|
|
567
|
+
def root_func_dploop(
|
|
568
|
+
self,
|
|
569
|
+
mflowscale: float,
|
|
570
|
+
dplooptarget: float,
|
|
571
|
+
tempfluid: np.ndarray,
|
|
572
|
+
flowrate: float,
|
|
573
|
+
effpump: float,
|
|
574
|
+
) -> float:
|
|
575
|
+
"""
|
|
576
|
+
Objective function used by ``findflowrate_dploop`` for root finding function.
|
|
577
|
+
|
|
578
|
+
Parameters
|
|
579
|
+
----------
|
|
580
|
+
mflowscale : float
|
|
581
|
+
Scaling factor applied to nominal flow rate.
|
|
582
|
+
dplooptarget : float
|
|
583
|
+
Target pumping pressure (bar).
|
|
584
|
+
tempfluid : ndarray
|
|
585
|
+
Fluid temperatures (°C).
|
|
586
|
+
flowrate : float
|
|
587
|
+
Nominal mass flow rate (kg/s).
|
|
588
|
+
effpump : float
|
|
589
|
+
Pump efficiency.
|
|
590
|
+
|
|
591
|
+
Returns
|
|
592
|
+
-------
|
|
593
|
+
float
|
|
594
|
+
Difference between actual and target pumping pressure.
|
|
595
|
+
"""
|
|
596
|
+
dploop, _ = self.dploop_nosyphon(tempfluid, flowrate * mflowscale, effpump)
|
|
597
|
+
|
|
598
|
+
return max(dploop) - dplooptarget
|
|
599
|
+
|
|
600
|
+
def calculate_dploop(
|
|
601
|
+
self, T_f: np.ndarray, z: np.ndarray, flowrate: np.ndarray, effpump: float
|
|
602
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
603
|
+
"""
|
|
604
|
+
Pumping pressure, pumping power and the Reynolds number are calculated for the in- and outlets in the loop. Assumption is that the flow and temperature are
|
|
605
|
+
divided equally and symmetrically over the pipes. (i.e. the pipe diameters of respectively the inlet and outlet
|
|
606
|
+
pipes is the same and the in- and oulet pipes alternate in the pipe design).
|
|
607
|
+
Re_in, Re_uit are stored over time (Re_in represents the Reynolds number that is the same in all inlets, Re_out represents the Reynolds number that represents all outlets)
|
|
608
|
+
|
|
609
|
+
Parameters
|
|
610
|
+
----------
|
|
611
|
+
T_f : ndarray
|
|
612
|
+
Fluid temperature profile [ntime, nz, npipes] (°C).
|
|
613
|
+
z : ndarray
|
|
614
|
+
Along hole depth discretization (m).
|
|
615
|
+
flowrate : ndarray
|
|
616
|
+
Mass flow rate time series (kg/s).
|
|
617
|
+
effpump : float
|
|
618
|
+
Pump efficiency.
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
dpsumtime : ndarray
|
|
623
|
+
Pumping pressure (bar) for each time.
|
|
624
|
+
qpump : ndarray
|
|
625
|
+
Pumping power (W) for each time.
|
|
626
|
+
"""
|
|
627
|
+
nz = len(z)
|
|
628
|
+
qpump = flowrate * 0
|
|
629
|
+
dpsumtime = flowrate * 0
|
|
630
|
+
|
|
631
|
+
ipipes = [0, self.n_p - 1]
|
|
632
|
+
npipes = [self.nInlets, self.n_p - self.nInlets]
|
|
633
|
+
rhosum = [0, 0]
|
|
634
|
+
|
|
635
|
+
Re_meanz = np.ones(
|
|
636
|
+
(len(ipipes), len(flowrate))
|
|
637
|
+
) # Store average Re for each time step
|
|
638
|
+
|
|
639
|
+
for itime in range(len(flowrate)):
|
|
640
|
+
dpsum = 0
|
|
641
|
+
|
|
642
|
+
for ip in range(len(ipipes)):
|
|
643
|
+
ipipe = ipipes[ip]
|
|
644
|
+
mflow = flowrate / (npipes[ip])
|
|
645
|
+
tz = T_f[itime, :, ipipe].reshape(nz)
|
|
646
|
+
Resum = 0
|
|
647
|
+
rhosum[ip] = 0
|
|
648
|
+
|
|
649
|
+
for k in range(nz):
|
|
650
|
+
try:
|
|
651
|
+
f = Fluid(self.fluid_str, self.fluid_percent, T=tz[k])
|
|
652
|
+
except:
|
|
653
|
+
print("Error in def dploop")
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
mu_f = f.dynamic_viscosity()
|
|
657
|
+
rho_f = f.density()
|
|
658
|
+
fDarcy, dpdl, Re = self.fluid_friction_factor(
|
|
659
|
+
ipipe, mflow[itime], mu_f, rho_f, self.epsilon
|
|
660
|
+
)
|
|
661
|
+
Resum += Re
|
|
662
|
+
|
|
663
|
+
if k > 0:
|
|
664
|
+
dpsum += 0.5 * dpdl * (z[k] - z[k - 1])
|
|
665
|
+
rhosum[ip] += rho_f * (z[k] - z[k - 1])
|
|
666
|
+
if k < len(tz) - 1:
|
|
667
|
+
dpsum += 0.5 * dpdl * (z[k + 1] - z[k])
|
|
668
|
+
rhosum[ip] += rho_f * (z[k + 1] - z[k])
|
|
669
|
+
|
|
670
|
+
# Store the depth-average reynold nr over depth for the different pipes over time in one array
|
|
671
|
+
Re_meanz[ip, itime] = Resum / nz
|
|
672
|
+
|
|
673
|
+
dprho = (rhosum[1] - rhosum[0]) * 9.81
|
|
674
|
+
|
|
675
|
+
dpsum += dprho
|
|
676
|
+
qpump[itime] = dpsum * (flowrate[itime] / rho_f) / effpump
|
|
677
|
+
dpsumtime[itime] = dpsum
|
|
678
|
+
|
|
679
|
+
# Store Re values for in- and outlet pipes separately
|
|
680
|
+
self.Re_in = Re_meanz[0]
|
|
681
|
+
self.Re_out = Re_meanz[1]
|
|
682
|
+
|
|
683
|
+
return dpsumtime / 1e5, qpump # Return pressure in bar
|