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,519 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pygfunction as gt
|
|
3
|
+
from matplotlib import pyplot as plt
|
|
4
|
+
from scipy.constants import pi
|
|
5
|
+
|
|
6
|
+
from geoloop.geoloopcore.boreholedesign import BoreholeDesign
|
|
7
|
+
from geoloop.geoloopcore.CoaxialPipe import CoaxialPipe
|
|
8
|
+
from geoloop.geoloopcore.CustomPipe import CustomPipe
|
|
9
|
+
from geoloop.geoloopcore.simulationparameters import SimulationParameters
|
|
10
|
+
from geoloop.geoloopcore.soilproperties import SoilProperties
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def inclination_with_depth(
|
|
14
|
+
depth: float, max_depth: float, initial_inclination: float, final_inclination: float
|
|
15
|
+
) -> float:
|
|
16
|
+
"""
|
|
17
|
+
Calculate inclination angle with depth.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
depth : float
|
|
22
|
+
Depth at which to calculate the inclination (m).
|
|
23
|
+
max_depth : float
|
|
24
|
+
Maximum depth of the borehole (m).
|
|
25
|
+
initial_inclination : float
|
|
26
|
+
Inclination angle at the surface (radians).
|
|
27
|
+
final_inclination : float
|
|
28
|
+
Inclination angle at maximum depth (radians).
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
inclination : float
|
|
33
|
+
Inclination angle at the given depth (radians).
|
|
34
|
+
"""
|
|
35
|
+
inclination = initial_inclination + (final_inclination - initial_inclination) * (
|
|
36
|
+
depth / max_depth
|
|
37
|
+
)
|
|
38
|
+
return inclination
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_curved_borehole(
|
|
42
|
+
H: float,
|
|
43
|
+
D: float,
|
|
44
|
+
x: float,
|
|
45
|
+
y: float,
|
|
46
|
+
initial_tilt: float,
|
|
47
|
+
final_tilt: float,
|
|
48
|
+
orientation: float,
|
|
49
|
+
num_segments: int = 10,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Create a borehole with varying inclination by approximating it with straight segments.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
H : float
|
|
57
|
+
Borehole length (m).
|
|
58
|
+
D : float
|
|
59
|
+
Borehole burial depth (m)
|
|
60
|
+
x : float
|
|
61
|
+
x-coordinate of the borehole.
|
|
62
|
+
y : float
|
|
63
|
+
y-coordinate of the borehole.
|
|
64
|
+
initial_tilt : float
|
|
65
|
+
Initial borehole inclination (radians).
|
|
66
|
+
final_tilt : float
|
|
67
|
+
Final borehole inclination (radians).
|
|
68
|
+
orientation : float
|
|
69
|
+
Borehole orientation angle (radians).
|
|
70
|
+
num_segments : int, optional
|
|
71
|
+
Number of segments to approximate the varying inclination. The default is 10.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
segments : list
|
|
76
|
+
List of segment coordinates representing the borehole.
|
|
77
|
+
(x_start, y_start, z_start, x_end, y_end, z_end, tilt, orientation)
|
|
78
|
+
"""
|
|
79
|
+
segment_length = H / num_segments
|
|
80
|
+
segments = []
|
|
81
|
+
|
|
82
|
+
# starting point
|
|
83
|
+
x_current, y_current = x, y
|
|
84
|
+
z_current = D
|
|
85
|
+
|
|
86
|
+
for i in range(num_segments):
|
|
87
|
+
# Depth at midpoint of the segment
|
|
88
|
+
depth = (i + 0.5) * segment_length
|
|
89
|
+
|
|
90
|
+
# local tilt at this depth
|
|
91
|
+
tilt = inclination_with_depth(depth, H, initial_tilt, final_tilt)
|
|
92
|
+
|
|
93
|
+
# compute offsets in 3D
|
|
94
|
+
x_offset = segment_length * np.sin(tilt) * np.cos(orientation)
|
|
95
|
+
y_offset = segment_length * np.sin(tilt) * np.sin(orientation)
|
|
96
|
+
z_offset = segment_length * np.cos(tilt)
|
|
97
|
+
|
|
98
|
+
# avoid degeneracy for perfectly vertical segments
|
|
99
|
+
if (x_offset == 0) and (y_offset == 0):
|
|
100
|
+
x_offset = max(1, x_offset)
|
|
101
|
+
|
|
102
|
+
x_end = x_current + x_offset
|
|
103
|
+
y_end = y_current + y_offset
|
|
104
|
+
z_end = z_current + z_offset
|
|
105
|
+
|
|
106
|
+
segments.append(
|
|
107
|
+
(x_current, y_current, z_current, x_end, y_end, z_end, tilt, orientation)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Move to next segment start
|
|
111
|
+
x_current, y_current, z_current = x_end, y_end, z_end
|
|
112
|
+
|
|
113
|
+
return segments
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def visualize_3d_borehole_field(borefield: list) -> "Figure":
|
|
117
|
+
"""
|
|
118
|
+
Produce a simple 3D matplotlib visualization of a borehole field.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
borefield : list
|
|
123
|
+
List of boreholes, each itself a list of segments
|
|
124
|
+
defined by (x_start, y_start, z_start, x_end, y_end, z_end, tilt, orientation).
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
matplotlib.figure.Figure
|
|
129
|
+
The resulting 3D figure.
|
|
130
|
+
"""
|
|
131
|
+
fig = plt.figure()
|
|
132
|
+
ax = fig.add_subplot(111, projection="3d")
|
|
133
|
+
|
|
134
|
+
for segments in borefield:
|
|
135
|
+
for (
|
|
136
|
+
x_start,
|
|
137
|
+
y_start,
|
|
138
|
+
z_start,
|
|
139
|
+
x_end,
|
|
140
|
+
y_end,
|
|
141
|
+
z_end,
|
|
142
|
+
tilt,
|
|
143
|
+
orientation,
|
|
144
|
+
) in segments:
|
|
145
|
+
ax.plot([x_start, x_end], [y_start, y_end], [-z_start, -z_end], "bo-")
|
|
146
|
+
|
|
147
|
+
ax.set_xlabel("X (m)")
|
|
148
|
+
ax.set_ylabel("Y (m)")
|
|
149
|
+
ax.set_zlabel("Depth (m)")
|
|
150
|
+
ax.set_title("3D Visualization of Borehole Field")
|
|
151
|
+
|
|
152
|
+
return fig
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def visualize_gfunc(gfunc: gt.gfunction) -> "Figure":
|
|
156
|
+
"""
|
|
157
|
+
Visualize a g-function using the built-in pygfunction plotting utility.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
gfunc : gt.gfunction.gFunction
|
|
162
|
+
Computed g-function object.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
matplotlib.figure.Figure
|
|
167
|
+
The plotted g-function.
|
|
168
|
+
"""
|
|
169
|
+
fig = gfunc.visualize_g_function()
|
|
170
|
+
fig.suptitle("g-function of borehole field")
|
|
171
|
+
fig.tight_layout()
|
|
172
|
+
return fig
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class PYGFIELD_ana:
|
|
176
|
+
"""
|
|
177
|
+
Field-level simulation driver using pygfunction and an analytical
|
|
178
|
+
approximation for inclined / curved boreholes.
|
|
179
|
+
|
|
180
|
+
This class builds a borehole field from a BoreholeDesign (which may include
|
|
181
|
+
inclined/curved boreholes approximated by segments), computes the g-function,
|
|
182
|
+
runs load aggregation and finally evaluates pipe temperatures using a
|
|
183
|
+
CustomPipe / CoaxialPipe model.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
bh_design: BoreholeDesign,
|
|
189
|
+
custom_pipe: CustomPipe,
|
|
190
|
+
soil_props: SoilProperties,
|
|
191
|
+
sim_params: SimulationParameters,
|
|
192
|
+
):
|
|
193
|
+
"""
|
|
194
|
+
Initialize the PYGFIELD_ana model with a given borehole field configuration.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
bh_design : BoreholeDesign
|
|
199
|
+
Design describing field layout, inclinations and borehole parameters.
|
|
200
|
+
custom_pipe : CustomPipe or CoaxialPipe
|
|
201
|
+
Pipe/borehole model used for temperature calculations.
|
|
202
|
+
soil_props : SoilProperties
|
|
203
|
+
Soil parameters and temperature profile provider.
|
|
204
|
+
sim_params : SimulationParameters
|
|
205
|
+
Simulation control parameters (time vector, loads, flow, run type).
|
|
206
|
+
"""
|
|
207
|
+
self.bh_design = bh_design
|
|
208
|
+
self.custom_pipe = custom_pipe
|
|
209
|
+
self.soil_props = soil_props
|
|
210
|
+
self.sim_params = sim_params
|
|
211
|
+
|
|
212
|
+
def runsimulation(self):
|
|
213
|
+
"""
|
|
214
|
+
Dispatch to the appropriate simulation routine based on run_type.
|
|
215
|
+
"""
|
|
216
|
+
if self.sim_params.run_type == SimulationParameters.POWER:
|
|
217
|
+
return self.runsimulation_power()
|
|
218
|
+
|
|
219
|
+
else:
|
|
220
|
+
# TIN mode is not implemented in this bhe field driver
|
|
221
|
+
raise NotImplementedError("run_type 'TIN' is not supported by PYGFIELD_ana")
|
|
222
|
+
|
|
223
|
+
def runsimulation_power(self) -> tuple:
|
|
224
|
+
"""
|
|
225
|
+
Run a power-driven simulation (POWER) for the borehole field.
|
|
226
|
+
|
|
227
|
+
Notes
|
|
228
|
+
------
|
|
229
|
+
The method:
|
|
230
|
+
- Converts the provided pipe model to a pygfunction-compatible object
|
|
231
|
+
(if necessary),
|
|
232
|
+
- Builds a borehole field according to BoreholeDesign (inclined/curved
|
|
233
|
+
boreholes are approximated by segments),
|
|
234
|
+
- Computes the g-function for the field,
|
|
235
|
+
- Initializes load aggregation (Claesson-Javed) and applies the simulated
|
|
236
|
+
loads, and
|
|
237
|
+
- Calls the pipe model to obtain fluid temperatures and extraction rates.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
tuple
|
|
242
|
+
(hours, Q_b, flowrate, qsign, T_fi, T_fo, T_bave, z, zseg, T_b,
|
|
243
|
+
T_ftimes, -qbzseg, h_fpipes)
|
|
244
|
+
"""
|
|
245
|
+
sim_params = self.sim_params
|
|
246
|
+
custom_pipe = self.custom_pipe
|
|
247
|
+
soil_props = self.soil_props
|
|
248
|
+
|
|
249
|
+
# Extract fluid properties from custom_pipe object
|
|
250
|
+
cp_f = custom_pipe.cp_f
|
|
251
|
+
|
|
252
|
+
# Convert the custompipe to the pyg singleutube design with adopted parameters
|
|
253
|
+
if isinstance(custom_pipe, CoaxialPipe):
|
|
254
|
+
coaxial = custom_pipe.create_coaxial()
|
|
255
|
+
custom_pipe = coaxial
|
|
256
|
+
elif isinstance(custom_pipe, CustomPipe):
|
|
257
|
+
multiUTube = custom_pipe.create_multi_u_tube()
|
|
258
|
+
custom_pipe = multiUTube
|
|
259
|
+
|
|
260
|
+
# only one segment simulated because no depthvar in pyg
|
|
261
|
+
sim_params.nsegments = 1
|
|
262
|
+
|
|
263
|
+
nsegments = sim_params.nsegments
|
|
264
|
+
|
|
265
|
+
# Load aggregation scheme
|
|
266
|
+
LoadAgg = gt.load_aggregation.ClaessonJaved(sim_params.dt, sim_params.tmax)
|
|
267
|
+
|
|
268
|
+
# Geometry and time arrays
|
|
269
|
+
H = custom_pipe.b.H
|
|
270
|
+
D = custom_pipe.b.D
|
|
271
|
+
Nt = sim_params.Nt
|
|
272
|
+
time = sim_params.time
|
|
273
|
+
m_flow = sim_params.m_flow[0]
|
|
274
|
+
scaleflow = sim_params.m_flow / m_flow
|
|
275
|
+
|
|
276
|
+
dz = H / nsegments
|
|
277
|
+
zmin = D + 0.5 * dz
|
|
278
|
+
zmax = D + H - 0.5 * dz
|
|
279
|
+
zseg = np.linspace(zmin, zmax, nsegments)
|
|
280
|
+
zz = np.linspace(D, D + H, nsegments + 1)
|
|
281
|
+
k_s = soil_props.get_k_s(zz[0:-1], zz[1:], sim_params.isample)
|
|
282
|
+
|
|
283
|
+
# Build borefield from BoreholeDesign
|
|
284
|
+
borehole_field = []
|
|
285
|
+
N = self.bh_design.N
|
|
286
|
+
M = self.bh_design.M
|
|
287
|
+
R = self.bh_design.R
|
|
288
|
+
num_segments = self.bh_design.num_tiltedsegments
|
|
289
|
+
|
|
290
|
+
print(
|
|
291
|
+
"N = total nr. of boreholes, "
|
|
292
|
+
"M = nr. of boreholes per side of the field, "
|
|
293
|
+
"R = Distance between boreholes",
|
|
294
|
+
N,
|
|
295
|
+
M,
|
|
296
|
+
R,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Circular / radial arrangement when M <= 0
|
|
300
|
+
if M <= 0:
|
|
301
|
+
initial_tilt = np.deg2rad(self.bh_design.inclination_start)
|
|
302
|
+
final_tilt = np.deg2rad(self.bh_design.inclination_end)
|
|
303
|
+
|
|
304
|
+
# Create curved boreholes and flatten to pyg Borehole objects
|
|
305
|
+
for i in range(N):
|
|
306
|
+
angle = 2 * np.pi * i / N
|
|
307
|
+
x = R * np.cos(angle)
|
|
308
|
+
y = R * np.sin(angle)
|
|
309
|
+
|
|
310
|
+
segments = create_curved_borehole(
|
|
311
|
+
custom_pipe.b.H,
|
|
312
|
+
custom_pipe.b.D,
|
|
313
|
+
x,
|
|
314
|
+
y,
|
|
315
|
+
initial_tilt,
|
|
316
|
+
final_tilt,
|
|
317
|
+
angle,
|
|
318
|
+
num_segments=num_segments,
|
|
319
|
+
)
|
|
320
|
+
borehole_field.append(segments)
|
|
321
|
+
|
|
322
|
+
self.borefield = borehole_field
|
|
323
|
+
|
|
324
|
+
borehole_field_flat = [
|
|
325
|
+
gt.boreholes.Borehole(
|
|
326
|
+
custom_pipe.b.H / num_segments,
|
|
327
|
+
z,
|
|
328
|
+
custom_pipe.b.r_b,
|
|
329
|
+
x,
|
|
330
|
+
y,
|
|
331
|
+
tilt=tilt,
|
|
332
|
+
orientation=orientation,
|
|
333
|
+
)
|
|
334
|
+
for segments in borehole_field
|
|
335
|
+
for (x, y, z, _, _, _, tilt, orientation) in segments
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
# Rectangular field arrangement (N must be multiple of M)
|
|
340
|
+
Ng = int(N / M)
|
|
341
|
+
if (Ng * M) != N:
|
|
342
|
+
print("N must be a multiple of M for agrid arrangement")
|
|
343
|
+
exit()
|
|
344
|
+
|
|
345
|
+
B = R
|
|
346
|
+
tilt = 0.5 * (
|
|
347
|
+
np.deg2rad(self.bh_design.inclination_start)
|
|
348
|
+
+ np.deg2rad(self.bh_design.inclination_end)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
borehole_field_flat = gt.borefield.Borefield.rectangle_field(
|
|
352
|
+
Ng,
|
|
353
|
+
M,
|
|
354
|
+
B,
|
|
355
|
+
B,
|
|
356
|
+
custom_pipe.b.H,
|
|
357
|
+
custom_pipe.b.D,
|
|
358
|
+
custom_pipe.b.r_b,
|
|
359
|
+
tilt=tilt,
|
|
360
|
+
)
|
|
361
|
+
gt.borefield.Borefield.visualize_field(borehole_field_flat)
|
|
362
|
+
|
|
363
|
+
self.borefield = borehole_field_flat
|
|
364
|
+
|
|
365
|
+
# Compute g-function for the built field
|
|
366
|
+
alpha = soil_props.alfa
|
|
367
|
+
method = "similarities"
|
|
368
|
+
options = {"nSegments": 1}
|
|
369
|
+
|
|
370
|
+
time_req = LoadAgg.get_times_for_simulation()
|
|
371
|
+
|
|
372
|
+
# Compute g-function (newer pygfunction versions accept keyword args alpha=, time=)
|
|
373
|
+
np.seterr(under="ignore")
|
|
374
|
+
stringbc = "UBWT"
|
|
375
|
+
gFunc = gt.gfunction.gFunction(
|
|
376
|
+
borehole_field_flat,
|
|
377
|
+
alpha,
|
|
378
|
+
time=time_req,
|
|
379
|
+
options=options,
|
|
380
|
+
method=method,
|
|
381
|
+
boundary_condition=stringbc,
|
|
382
|
+
)
|
|
383
|
+
np.seterr(under="warn")
|
|
384
|
+
|
|
385
|
+
# Store for later inspection / plotting
|
|
386
|
+
self.gfunc = gFunc
|
|
387
|
+
|
|
388
|
+
# Initialize load aggregation scheme
|
|
389
|
+
LoadAgg.initialize(gFunc.gFunc / (2 * pi * k_s))
|
|
390
|
+
|
|
391
|
+
Qabsmin = H * 0.1 # assume at least 0.1 W /m to avoid division by zero
|
|
392
|
+
|
|
393
|
+
# Delta temperatures are calculated at the segments
|
|
394
|
+
deltaT_b = np.zeros(Nt)
|
|
395
|
+
deltaT_bk = np.zeros((Nt, nsegments))
|
|
396
|
+
power = np.zeros(Nt)
|
|
397
|
+
Q_b = sim_params.Q / N # per-borehole load
|
|
398
|
+
|
|
399
|
+
for i, (t, Q_b_i) in enumerate(zip(time, Q_b)):
|
|
400
|
+
# Increment time step by (1)
|
|
401
|
+
LoadAgg.next_time_step(t)
|
|
402
|
+
|
|
403
|
+
# avoid the Q_B_i to be zero, it is at least the 10% of H in watts
|
|
404
|
+
if abs(Q_b_i) < Qabsmin:
|
|
405
|
+
Q_b_i = Qabsmin * np.sign(Q_b_i)
|
|
406
|
+
|
|
407
|
+
# Apply current load
|
|
408
|
+
LoadAgg.set_current_load(Q_b_i / H)
|
|
409
|
+
|
|
410
|
+
deltaT_bk[i] = LoadAgg.temporal_superposition()
|
|
411
|
+
deltaT_b[i] = LoadAgg.temporal_superposition()
|
|
412
|
+
power[i] = Q_b_i
|
|
413
|
+
|
|
414
|
+
# Prepare storage arrays
|
|
415
|
+
T_b_top = np.zeros(Nt)
|
|
416
|
+
T_bave = np.zeros(Nt)
|
|
417
|
+
T_fi = np.zeros(Nt)
|
|
418
|
+
T_fo = np.zeros(Nt)
|
|
419
|
+
|
|
420
|
+
Q_b = np.zeros(Nt)
|
|
421
|
+
deltaT_b_ref = np.zeros(Nt)
|
|
422
|
+
flowrate = np.zeros(Nt)
|
|
423
|
+
Rs = np.zeros(Nt)
|
|
424
|
+
qsign = np.zeros(Nt)
|
|
425
|
+
|
|
426
|
+
imax = -1
|
|
427
|
+
for i, t in enumerate(time):
|
|
428
|
+
if (i > imax) and (i < Nt):
|
|
429
|
+
p = power[i]
|
|
430
|
+
Rs[i] = deltaT_b[i] / (p / H)
|
|
431
|
+
|
|
432
|
+
minR = 0.01
|
|
433
|
+
Rs = np.where(np.logical_and(Rs < minR, Rs > -0.5), minR, Rs)
|
|
434
|
+
|
|
435
|
+
# Iterative refinement to compute pipe & borehole temperatures
|
|
436
|
+
iter = 0
|
|
437
|
+
niter = 3
|
|
438
|
+
# Rsz not filled because not used in pyg-function
|
|
439
|
+
qbzseg = np.zeros((len(Rs), nsegments))
|
|
440
|
+
T_b = np.zeros((len(Rs), nsegments))
|
|
441
|
+
h_fpipes = np.zeros(
|
|
442
|
+
(len(Rs), (custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# nPipes is defined differently for standard pyg and the analytical model; in pyg nPipes defines the number of utubes,
|
|
446
|
+
# in the analytical model nPipes defines the nr of pipes
|
|
447
|
+
nz = 20
|
|
448
|
+
z = np.linspace(0.0, H, num=nz)
|
|
449
|
+
T_ftimes = np.zeros(
|
|
450
|
+
(
|
|
451
|
+
len(Rs),
|
|
452
|
+
nz,
|
|
453
|
+
(custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes,
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# soil temperature
|
|
458
|
+
T_g = soil_props.getTg(zseg)
|
|
459
|
+
|
|
460
|
+
while iter < niter:
|
|
461
|
+
iter += 1
|
|
462
|
+
imax = -1
|
|
463
|
+
|
|
464
|
+
for i, t in enumerate(time):
|
|
465
|
+
if (i > imax) and (i < Nt):
|
|
466
|
+
p = power[i]
|
|
467
|
+
h_f = custom_pipe.h_f
|
|
468
|
+
|
|
469
|
+
T_b[i] = np.asarray(T_g - deltaT_b[i])
|
|
470
|
+
|
|
471
|
+
T_f_in = custom_pipe.get_inlet_temperature(
|
|
472
|
+
p, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
473
|
+
)
|
|
474
|
+
T_f_out = custom_pipe.get_outlet_temperature(
|
|
475
|
+
T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# To compare between depth variation between pyg and ana; Evaluate temperatures fro pyg at nz evenly spaced depths along the borehole
|
|
479
|
+
# at the (it+1)-th time step
|
|
480
|
+
T_f = custom_pipe.get_temperature(
|
|
481
|
+
z, T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
qbz = custom_pipe.get_total_heat_extraction_rate(
|
|
485
|
+
T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Store outputs per time-step
|
|
489
|
+
Q_b[i] = power[i]
|
|
490
|
+
T_b_top[i] = T_b[i][0]
|
|
491
|
+
deltaT_b_ref[i] = deltaT_b[i]
|
|
492
|
+
T_bave[i] = np.average(T_b[i, :])
|
|
493
|
+
T_fi[i] = T_f_in
|
|
494
|
+
T_fo[i] = T_f_out
|
|
495
|
+
h_fpipes[i] = h_f
|
|
496
|
+
flowrate[i] = scaleflow[i] * m_flow
|
|
497
|
+
T_ftimes[i] = T_f # stored index time, depth, pipe
|
|
498
|
+
# zseg 1 value less than z
|
|
499
|
+
qbzseg[i] = qbz / custom_pipe.b.H
|
|
500
|
+
qsign[i] = np.sign(qbz)
|
|
501
|
+
imax = i
|
|
502
|
+
|
|
503
|
+
hours = time / 3600.0
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
hours,
|
|
507
|
+
Q_b,
|
|
508
|
+
flowrate,
|
|
509
|
+
qsign,
|
|
510
|
+
T_fi,
|
|
511
|
+
T_fo,
|
|
512
|
+
T_bave,
|
|
513
|
+
z,
|
|
514
|
+
zseg,
|
|
515
|
+
T_b,
|
|
516
|
+
T_ftimes,
|
|
517
|
+
-qbzseg,
|
|
518
|
+
h_fpipes,
|
|
519
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from geoloop.configuration import SingleRunConfig
|
|
4
|
+
from geoloop.geoloopcore.getloaddata import GetLoadData
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SimulationParameters:
|
|
8
|
+
"""
|
|
9
|
+
Class for simulation settings, time discretization,
|
|
10
|
+
load data, and model selection for BHE simulations.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# model types
|
|
14
|
+
FINVOL = "FINVOL" # finite volume approach with borehole heat exchanger system, can only run with run_type TIN
|
|
15
|
+
ANALYTICAL = (
|
|
16
|
+
"ANALYTICAL" # semi-analytical approach, can run with both TIN and POWER
|
|
17
|
+
)
|
|
18
|
+
PYG = "PYG" # 'standard' pyg approach, only supports run type POWER
|
|
19
|
+
PYGFIELD = (
|
|
20
|
+
"PYGFIELD" # pyg curved borehole array approach, only supports run type POWER
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# run types
|
|
24
|
+
TIN = "TIN" # input temperature
|
|
25
|
+
POWER = "POWER" # input power
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
nyear: int,
|
|
30
|
+
nled: int,
|
|
31
|
+
model_type: str,
|
|
32
|
+
run_type: str,
|
|
33
|
+
nsegments: int,
|
|
34
|
+
nr: int = None,
|
|
35
|
+
rmax: float = None,
|
|
36
|
+
loaddata: GetLoadData = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Constructor for the SimulationParameters class.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
nyear : int
|
|
44
|
+
Number of simulation years.
|
|
45
|
+
nled : int
|
|
46
|
+
Time resolution factor (dt = 3600 * nled).
|
|
47
|
+
model_type : str
|
|
48
|
+
One of {FINVOL, ANALYTICAL, PYG, PYGFIELD}.
|
|
49
|
+
run_type : str
|
|
50
|
+
Either TIN or POWER.
|
|
51
|
+
nsegments : int
|
|
52
|
+
Number of depth segments used in the borehole solver.
|
|
53
|
+
nr : int, optional
|
|
54
|
+
Number of radial nodes (FINVOL only).
|
|
55
|
+
rmax : float, optional
|
|
56
|
+
Outer radius for radial simulation domain (FINVOL only).
|
|
57
|
+
loaddata : GetLoadData
|
|
58
|
+
Load data object providing Tin, Q, m_flow, and time arrays.
|
|
59
|
+
"""
|
|
60
|
+
# time discretization
|
|
61
|
+
self.nyear = nyear
|
|
62
|
+
self.tmax = nyear * 8760.0 * 3600.0 # Maximum time (s)
|
|
63
|
+
self.nled = nled
|
|
64
|
+
self.dt = 3600 * nled # Time step (s) , 100h
|
|
65
|
+
self.Nt = int(np.ceil(self.tmax / self.dt)) # Number of time steps
|
|
66
|
+
self.time = self.dt * np.arange(1, self.Nt + 1) # time in seconds
|
|
67
|
+
|
|
68
|
+
# Geometry parameters only used in the FINVOL model
|
|
69
|
+
self.nr = nr
|
|
70
|
+
self.rmax = rmax
|
|
71
|
+
|
|
72
|
+
# load data
|
|
73
|
+
self.loaddata = loaddata.data
|
|
74
|
+
|
|
75
|
+
self.m_flow = loaddata.m_flow
|
|
76
|
+
self.Tin = loaddata.Tin
|
|
77
|
+
self.Q = loaddata.Q
|
|
78
|
+
|
|
79
|
+
# model configuration
|
|
80
|
+
self.model_type = model_type
|
|
81
|
+
self.run_type = run_type
|
|
82
|
+
self.nsegments = nsegments
|
|
83
|
+
|
|
84
|
+
# set the efficency of the pump to 0.65
|
|
85
|
+
self.eff = 0.65
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def isample(self):
|
|
89
|
+
"""Index of the current simulation timestep."""
|
|
90
|
+
return self._isample
|
|
91
|
+
|
|
92
|
+
@isample.setter
|
|
93
|
+
def isample(self, value: int) -> None:
|
|
94
|
+
self._isample = value
|
|
95
|
+
|
|
96
|
+
def modelsupported(self) -> None:
|
|
97
|
+
"""Prints a warning for unsupported model/run combinations."""
|
|
98
|
+
print("Model not supported ", self.model_type, " with mode ", self)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_config(cls, config: SingleRunConfig) -> "SimulationParameters":
|
|
102
|
+
"""
|
|
103
|
+
Construct a SimulationParameters object from a configuration object.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
config : SingleRunConfig
|
|
108
|
+
Configuration object.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
SimulationParameters
|
|
113
|
+
Fully initialized simulation parameter object.
|
|
114
|
+
"""
|
|
115
|
+
nr = config.nr if config.model_type == cls.FINVOL else None
|
|
116
|
+
rmax = config.r_sim if config.model_type == cls.FINVOL else None
|
|
117
|
+
|
|
118
|
+
# Build GetLoadData only if present
|
|
119
|
+
load_data_obj = GetLoadData.from_config(config)
|
|
120
|
+
|
|
121
|
+
return cls(
|
|
122
|
+
nyear=config.nyear,
|
|
123
|
+
nled=config.nled,
|
|
124
|
+
model_type=config.model_type,
|
|
125
|
+
run_type=config.run_type,
|
|
126
|
+
nsegments=config.nsegments,
|
|
127
|
+
nr=nr,
|
|
128
|
+
rmax=rmax,
|
|
129
|
+
loaddata=load_data_obj,
|
|
130
|
+
)
|