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.
Files changed (47) hide show
  1. geoloop/axisym/AxisymetricEL.py +751 -0
  2. geoloop/axisym/__init__.py +3 -0
  3. geoloop/bin/Flowdatamain.py +89 -0
  4. geoloop/bin/Lithologymain.py +84 -0
  5. geoloop/bin/Loadprofilemain.py +100 -0
  6. geoloop/bin/Plotmain.py +250 -0
  7. geoloop/bin/Runbatch.py +81 -0
  8. geoloop/bin/Runmain.py +86 -0
  9. geoloop/bin/SingleRunSim.py +928 -0
  10. geoloop/bin/__init__.py +3 -0
  11. geoloop/cli/__init__.py +0 -0
  12. geoloop/cli/batch.py +106 -0
  13. geoloop/cli/main.py +105 -0
  14. geoloop/configuration.py +946 -0
  15. geoloop/constants.py +112 -0
  16. geoloop/geoloopcore/CoaxialPipe.py +503 -0
  17. geoloop/geoloopcore/CustomPipe.py +727 -0
  18. geoloop/geoloopcore/__init__.py +3 -0
  19. geoloop/geoloopcore/b2g.py +739 -0
  20. geoloop/geoloopcore/b2g_ana.py +535 -0
  21. geoloop/geoloopcore/boreholedesign.py +683 -0
  22. geoloop/geoloopcore/getloaddata.py +112 -0
  23. geoloop/geoloopcore/pyg_ana.py +280 -0
  24. geoloop/geoloopcore/pygfield_ana.py +519 -0
  25. geoloop/geoloopcore/simulationparameters.py +130 -0
  26. geoloop/geoloopcore/soilproperties.py +152 -0
  27. geoloop/geoloopcore/strat_interpolator.py +194 -0
  28. geoloop/lithology/__init__.py +3 -0
  29. geoloop/lithology/plot_lithology.py +277 -0
  30. geoloop/lithology/process_lithology.py +697 -0
  31. geoloop/loadflowdata/__init__.py +3 -0
  32. geoloop/loadflowdata/flow_data.py +161 -0
  33. geoloop/loadflowdata/loadprofile.py +325 -0
  34. geoloop/plotting/__init__.py +3 -0
  35. geoloop/plotting/create_plots.py +1137 -0
  36. geoloop/plotting/load_data.py +432 -0
  37. geoloop/utils/RunManager.py +164 -0
  38. geoloop/utils/__init__.py +0 -0
  39. geoloop/utils/helpers.py +841 -0
  40. geoloop-1.0.0b1.dist-info/METADATA +112 -0
  41. geoloop-1.0.0b1.dist-info/RECORD +46 -0
  42. geoloop-1.0.0b1.dist-info/entry_points.txt +2 -0
  43. geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0b1.dist-info/licenses/LICENSE.md +2 -1
  44. geoloop-0.0.1.dist-info/METADATA +0 -10
  45. geoloop-0.0.1.dist-info/RECORD +0 -6
  46. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/WHEEL +0 -0
  47. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/top_level.txt +0 -0
geoloop/constants.py ADDED
@@ -0,0 +1,112 @@
1
+ from pathlib import Path
2
+
3
+ repo_path = Path(__file__).parent.parent.parent
4
+
5
+ # Test directories
6
+ test_dir = repo_path / "test"
7
+ tests_input_path = test_dir / "input"
8
+ test_output_path = test_dir / "output"
9
+
10
+ # Resources directory
11
+ resources_path = repo_path / "resources"
12
+
13
+ # Lithology resources directory
14
+ lithology_properties_xlsx = (
15
+ resources_path / "lithology_properties" / "lithology_properties.xlsx"
16
+ )
17
+
18
+ # Dictionary of units for various parameters in the model.
19
+ units_dict = {
20
+ "Q_b": "W",
21
+ "flowrate": "kg/s",
22
+ "T_fi": "\u00b0C",
23
+ "T_fo": "\u00b0C",
24
+ "T_bave": "\u00b0C",
25
+ "dploop": "bar",
26
+ "qloop": "W",
27
+ "z": "m",
28
+ "T_b": "\u00b0C",
29
+ "T_f": "\u00b0C",
30
+ "qzb": "W/m",
31
+ "k_g": "W/mK",
32
+ "k_p": "W/mK",
33
+ "m_flow": "kg/s",
34
+ "Tin": "\u00b0C",
35
+ "H": "m",
36
+ "epsilon": "m",
37
+ "alfa": "m\u00b2/s",
38
+ "Tgrad": "\u00b0C/100m",
39
+ "Q": "W",
40
+ "qsign": "-",
41
+ "nPipes": "-",
42
+ "zseg": "-",
43
+ "k_s_scale": "-",
44
+ "Re_in": "-",
45
+ "Re_out": "-",
46
+ "h_f_max": "-",
47
+ "h_f_min": "-",
48
+ "k_s": "W/mK",
49
+ "k_s_res": "W/mK",
50
+ "fluid_percent": "%",
51
+ "insu_z": "m",
52
+ "insu_dr": "%",
53
+ "k_g_res": "W/mK",
54
+ }
55
+
56
+ # Dictionary of formatted names for various parameters in the model.
57
+ format_dict = {
58
+ "Q_b": "$Q_{b}$",
59
+ "flowrate": "$flowrate$",
60
+ "T_fi": "$T_{fi}$",
61
+ "T_fo": "$T_{fo}$",
62
+ "T_bave": "$T_{bave}$",
63
+ "dploop": "$dploop$",
64
+ "qloop": "$qloop$",
65
+ "z": "$z$",
66
+ "T_b": "$T_{b}$",
67
+ "T_f": "$T_{f}$",
68
+ "qzb": "$qzb$",
69
+ "k_g": "$k_{g}$",
70
+ "k_p": "$k_{p}$",
71
+ "m_flow": "$m_{flow}$",
72
+ "Tin": "$Tin$",
73
+ "H": "$H$",
74
+ "epsilon": r"$\epsilon$",
75
+ "alfa": r"$\alpha$",
76
+ "Tgrad": "$T_{grad}$",
77
+ "Q": "$Q$",
78
+ "qsign": "$q_{sign}$",
79
+ "nPipes": "$n_{Pipes}$",
80
+ "zseg": "$z_{seg}$",
81
+ "k_s_scale": "$k_{s}scale$",
82
+ "Re_in": "$Re_{in}$",
83
+ "Re_out": "$Re_{out}$",
84
+ "h_f_max": "$h_{f}max$",
85
+ "h_f_min": "$h_{f}min$",
86
+ "k_s": "$k_{s}$",
87
+ "k_s_res": "$k_{s-res}$",
88
+ "fluid_percent": "$fluid_{percent}$",
89
+ "insu_z": "$insu_{z}$",
90
+ "insu_dr": "$insu_{dr}$",
91
+ "k_g_res": "$k_{g-res}$",
92
+ }
93
+
94
+ # Dictionary with colors for lithology plotting
95
+ lithology_colors = {
96
+ "sand": "#FFFF00",
97
+ "clay": "#008000",
98
+ "shells": "#800080",
99
+ "chalk": "#FFFFFF",
100
+ "marl": "#A52A2A",
101
+ "claystone": "#1F1F1F",
102
+ }
103
+
104
+ # Dictionary for mapping lithology names from Dutch to English
105
+ lithology_names_english = {
106
+ "zand": "Sand",
107
+ "klei": "Clay",
108
+ "schelpen": "Shells",
109
+ "krijt": "Chalk",
110
+ "mergel": "Marl",
111
+ "kleisteen": "Claystone",
112
+ }
@@ -0,0 +1,503 @@
1
+ import numpy as np
2
+ import pygfunction as gt
3
+
4
+ from geoloop.geoloopcore.CustomPipe import CustomPipe, thermal_resistance_pipe
5
+ from geoloop.geoloopcore.soilproperties import SoilProperties
6
+
7
+
8
+ def Swap(arr: np.ndarray, start_index: int, last_index: int) -> None:
9
+ """
10
+ Swap two columns in an array, in place, from start_index to last_index.
11
+
12
+ Parameters
13
+ ----------
14
+ arr : np.ndarray
15
+ 2D array where columns will be swapped.
16
+ start_index : int
17
+ Index of the first column to swap.
18
+ last_index : int
19
+ Index of the second column to swap.
20
+
21
+ Returns
22
+ -------
23
+ None
24
+ The operation is performed in place.
25
+ """
26
+
27
+ arr[:, [start_index, last_index]] = arr[:, [last_index, start_index]]
28
+
29
+
30
+ class CoaxialPipe(CustomPipe):
31
+ """
32
+ This class is used to evaluate the thermal resistance network of a Coaxial borehole.
33
+
34
+ In its default mode the object
35
+ is marked by depth-indepedent design properties which can be altered, and can also access other methods from its baseclass
36
+ CustomPipe.
37
+
38
+ It uses pygfunction of Cimmino and Cook [#Cimmino2024]
39
+ for the determination of the thermal resistivity network of the borehole
40
+
41
+ It contains information regarding the physical dimensions and thermal
42
+ characteristics of the pipes and the grout material, as well as methods (through its base class) to
43
+ evaluate fluid temperatures and heat extraction rates based on the work of
44
+ Hellstrom [#Single-Hellstrom1991]_. Internal borehole thermal resistances
45
+ are evaluated using the multipole method of Claesson and Hellstrom
46
+ [#Single-Claesson2011b]_.
47
+
48
+ References
49
+ ----------
50
+ .. [#Cimmino2022] Cimmino, M., & Cook, J.C. (2022). pygfunction 2.2: New features and improvements in accuracy and computational efficiency.
51
+ In Research Conference Proceedings, IGSHPA Annual Conference 2022 (pp. 45-52).
52
+ International Ground Source Heat Pump Association. DOI: https://doi.org/10.22488/okstate.22.000015.
53
+ .. [#Single-Hellstrom1991] Hellstrom, G. (1991). Ground heat storage.
54
+ Thermal Analyses of Duct Storage Systems I: Theory. PhD Thesis.
55
+ University of Lund, Department of Mathematical Physics. Lund, Sweden.
56
+ .. [#Single-Claesson2011b] Claesson, J., & Hellstrom, G. (2011).
57
+ Multipole method to calculate borehole thermal resistances in a borehole
58
+ heat exchanger. HVAC&R Research, 17(6), 895-911.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ r_in: np.ndarray | float,
64
+ r_out: np.ndarray | float,
65
+ borehole,
66
+ k_g: float | np.ndarray,
67
+ k_p: float | np.ndarray,
68
+ k_s: float = 1.0,
69
+ J: int = 2,
70
+ m_flow: float = 1.0,
71
+ T_f: float = 10.0,
72
+ fluid_str: str = "Water",
73
+ percent: float = 100.0,
74
+ epsilon: float = 1e-6,
75
+ ncalcsegments: int = 1,
76
+ R_p: list[np.ndarray] | None = None,
77
+ ) -> None:
78
+ """
79
+ Initialize a coaxial borehole pipe model and compute its thermal and
80
+ hydraulic properties.
81
+
82
+ Parameters
83
+ ----------
84
+ r_in : float or ndarray
85
+ Inner radii of the coaxial pipes (m). If scalar, copied for both
86
+ inlet and outlet pipes. (first radius is largest as this is corresponding to the inlet)
87
+ r_out : float or ndarray
88
+ Outer radii of the coaxial pipes (m). If scalar, copied for both
89
+ inlet and outlet pipes.
90
+ borehole : gt.boreholes.Borehole
91
+ Borehole geometry object.
92
+ k_g : float or ndarray
93
+ Grout thermal conductivity (W/m·K). If array-like, represents
94
+ values per segment.
95
+ k_p : float or ndarray
96
+ Pipe wall thermal conductivity (W/m·K).
97
+ k_s : float, optional
98
+ Soil thermal conductivity (W/m·K). Default is 1.0.
99
+ J : int, optional
100
+ Number of multipoles used in the multipole expansion. Default is 2.
101
+ m_flow : float, optional
102
+ Total mass flow rate in the BHE (kg/s).
103
+ T_f : float, optional
104
+ Fluid temperature at inlet (°C).
105
+ fluid_str : str, optional
106
+ Working fluid name for thermal property lookup.
107
+ percent : float, optional
108
+ Concentration of glycol or other additive (%).
109
+ epsilon : float, optional
110
+ Relative pipe roughness for hydraulic calculations.
111
+ ncalcsegments : int, optional
112
+ Number of depth segments used in thermal resistance evaluation.
113
+ R_p : list of ndarray, optional
114
+ Precomputed pipe thermal resistances for each segment. If None,
115
+ resistances are computed automatically. Default is empty list, and routines will calculate the resistivity based on the
116
+ pipe dimensions and thermal conductivity.
117
+
118
+ Notes
119
+ -----
120
+ - Creates a :class:`gt.pipes.Coaxial` object for use in pygfunction.
121
+ - Computes convective coefficients, fluid properties, and initial
122
+ delta-circuit thermal resistance matrices.
123
+ """
124
+
125
+ self.pos = [(0, 0), (0, 0)]
126
+ self.nPipes = 2
127
+ if np.isscalar(r_in):
128
+ r_in = r_in * np.ones(self.nPipes)
129
+ self.r_in = r_in
130
+ if np.isscalar(r_out):
131
+ r_out = r_out * np.ones(self.nPipes)
132
+ self.r_out = r_out
133
+ self.b = borehole
134
+ self.k_s = 0.01
135
+ self.k_g = k_g
136
+ self.k_p = k_p
137
+ self.J = J
138
+ self.nInlets = 1
139
+ self._iOuter = 0
140
+ self._iInner = 1
141
+ self.nOutlets = self.nPipes - self.nInlets
142
+ self.ncalcsegments = ncalcsegments
143
+
144
+ # Pipe thermal resistances
145
+ # create a list of R_p if required
146
+ if len(R_p) == 0:
147
+ rp = thermal_resistance_pipe(r_in, r_out, k_p)
148
+ self.R_p = []
149
+ for i in range(ncalcsegments):
150
+ self.R_p.append(rp)
151
+ else:
152
+ self.R_p = R_p
153
+
154
+ # Initialize flow rate and fluid properties including fluid resistivity with pipes
155
+ self.m_flow = m_flow
156
+ self.m_flow_pipe = m_flow * np.ones(self.nPipes)
157
+ self.m_flow_pipe[: self.nInlets] = m_flow / self.nInlets
158
+ self.m_flow_pipe[self.nInlets :] = -m_flow / self.nOutlets
159
+ fluid = gt.media.Fluid(fluid_str, percent, T=T_f)
160
+ self.cp_f = fluid.cp # Fluid specific isobaric heat capacity (J/kg.K)
161
+ self.rho_f = fluid.rho # Fluid density (kg/m3)
162
+ self.mu_f = fluid.mu # Fluid dynamic viscosity (kg/m.s)
163
+ self.k_f = fluid.k # Fluid thermal conductivity (W/m.K)
164
+ self.epsilon = epsilon
165
+
166
+ # default Pipe thermal resistance (take from the first segment)
167
+ # Inner pipe
168
+ R_p_in = self.R_p[0][self._iInner]
169
+ # Outer pipe
170
+ R_p_out = self.R_p[0][self._iOuter]
171
+
172
+ # Fluid-to-fluid thermal resistance
173
+ # Inner pipe
174
+ r_in_in = self.r_in[self._iInner]
175
+ h_f_in = gt.pipes.convective_heat_transfer_coefficient_circular_pipe(
176
+ m_flow, r_in_in, self.mu_f, self.rho_f, self.k_f, self.cp_f, self.epsilon
177
+ )
178
+ R_f_in = 1.0 / (h_f_in * 2 * np.pi * r_in_in)
179
+ # Outer pipe
180
+ r_in_out = self.r_out[self._iInner]
181
+ r_out_in = self.r_out[self._iOuter]
182
+ h_f_a_in, h_f_a_out = (
183
+ gt.pipes.convective_heat_transfer_coefficient_concentric_annulus(
184
+ m_flow,
185
+ r_in_out,
186
+ r_out_in,
187
+ self.mu_f,
188
+ self.rho_f,
189
+ self.k_f,
190
+ self.cp_f,
191
+ self.epsilon,
192
+ )
193
+ )
194
+ R_f_out_in = 1.0 / (h_f_a_in * 2 * np.pi * r_in_out)
195
+ R_ff = R_f_in + R_p_in + R_f_out_in
196
+
197
+ # Coaxial GHE in borehole
198
+ R_f_out_out = 1.0 / (h_f_a_out * 2 * np.pi * r_out_in)
199
+ R_fp = R_p_out + R_f_out_out
200
+
201
+ r_inner = np.roll(self.r_in, 1)
202
+ r_outer = np.roll(self.r_out, 1)
203
+
204
+ self.coaxial = gt.pipes.Coaxial(
205
+ (0, 0), r_inner, r_outer, self.b, k_s, k_g[0], R_ff, R_fp, J=self.J
206
+ )
207
+
208
+ self.h_f = np.zeros(2)
209
+
210
+ self.Rd = []
211
+ self.update_scaleflow(1.0, forceupdate=True)
212
+
213
+ return
214
+
215
+ def create_coaxial(self) -> gt.pipes.Coaxial:
216
+ """
217
+ Return the underlying pygfunction :class:`Coaxial` object.
218
+
219
+ Returns
220
+ -------
221
+ gt.pipes.Coaxial
222
+ The pygfunction coaxial borehole object representing the thermal
223
+ resistance network.
224
+ """
225
+ return self.coaxial
226
+
227
+ def init_thermal_resistances(self, k_g: np.ndarray, R_p: list[np.ndarray]) -> None:
228
+ """
229
+ Initialize pipe and grout thermal resistances for all borehole segments.
230
+
231
+ This routine is called from the B2G.runsimulation method, in order to generate thermal resistances based
232
+ on actual segments determined by len(k_g) and len(R_p).
233
+
234
+ Parameters
235
+ ----------
236
+ k_g : ndarray
237
+ Grout thermal conductivity values for each segment.
238
+ R_p : list of ndarray
239
+ Pipe thermal resistance values for each segment.
240
+ """
241
+ self.ncalcsegments = len(k_g)
242
+ self.k_g = k_g
243
+ self.update_scaleflow(1.0)
244
+ self.R_p = R_p
245
+
246
+ def update_scaleflow(
247
+ self,
248
+ scaleflow: float = 1.0,
249
+ forceupdate: bool = False,
250
+ initialize_stored_coeff: bool = True,
251
+ ) -> None:
252
+ """
253
+ Update the scaling of flow rate and associated thermal resistance network.
254
+
255
+ This method adjusts the flow rate scaling and updates the thermal resistance
256
+ network based on the new flow rate. Optionally, it can force an update and
257
+ reinitialize stored coefficients.
258
+
259
+ Parameters
260
+ ----------
261
+ scaleflow : float, optional
262
+ Scaling factor for flow rate. Default is 1.0.
263
+ forceupdate : bool, optional
264
+ If True, forces update of thermal resistances even if changes are small.
265
+ Default is False.
266
+ initialize_stored_coeff : bool, optional
267
+ If True, reinitializes stored thermal resistance coefficients.
268
+ Default is True.
269
+
270
+ Returns
271
+ -------
272
+ None
273
+ Updates internal state in place.
274
+ """
275
+ self.scaleflow = scaleflow
276
+ m_flow = abs(self.m_flow_pipe[0] * self.scaleflow)
277
+
278
+ # Pipe thermal resistance (the two pipes have the same thermal conductivity, k_p)
279
+ # Fluid-to-fluid thermal resistance
280
+ # Inner pipe
281
+ r_in_in = self.r_in[self._iInner]
282
+ h_f_in = gt.pipes.convective_heat_transfer_coefficient_circular_pipe(
283
+ m_flow, r_in_in, self.mu_f, self.rho_f, self.k_f, self.cp_f, self.epsilon
284
+ )
285
+ R_f_in = 1.0 / (h_f_in * 2 * np.pi * r_in_in)
286
+ # Outer pipe
287
+ r_in_out = self.r_out[self._iInner]
288
+ r_out_in = self.r_out[self._iOuter]
289
+ h_f_a_in, h_f_a_out = (
290
+ gt.pipes.convective_heat_transfer_coefficient_concentric_annulus(
291
+ m_flow,
292
+ r_in_out,
293
+ r_out_in,
294
+ self.mu_f,
295
+ self.rho_f,
296
+ self.k_f,
297
+ self.cp_f,
298
+ self.epsilon,
299
+ )
300
+ )
301
+
302
+ R_f_out_in = 1.0 / (h_f_a_in * 2 * np.pi * r_in_out)
303
+ R_f_out_out = 1.0 / (h_f_a_out * 2 * np.pi * r_out_in)
304
+
305
+ hfnew = np.asarray([h_f_a_out, h_f_a_in])
306
+ hfdif = np.subtract(hfnew, self.h_f)
307
+ hfdot = np.dot(hfdif, hfdif)
308
+
309
+ if not forceupdate and self.ncalcsegments == len(self.Rd) and (hfdot < 1):
310
+ return
311
+
312
+ self.h_f = np.asarray([h_f_a_out, h_f_a_in])
313
+ self.coaxial.h_f = self.h_f
314
+
315
+ # --- recompute segment resistances ---
316
+ self.R = []
317
+ self.Rd = []
318
+
319
+ for k in range(self.ncalcsegments):
320
+ # Delta-circuit thermal resistances
321
+ # Inner pipe
322
+ R_p_in = self.R_p[k][self._iInner]
323
+ # Outer pipe
324
+ R_p_out = self.R_p[k][self._iOuter]
325
+
326
+ R_ff = R_f_in + R_p_in + R_f_out_in
327
+ R_fp = R_p_out + R_f_out_out
328
+
329
+ if k == 0:
330
+ self.coaxial.update_thermal_resistances(R_ff, R_fp)
331
+
332
+ R_fg = gt.pipes.thermal_resistances(
333
+ self.pos[0:1],
334
+ self.r_out[self._iOuter],
335
+ self.b.r_b,
336
+ self.k_s,
337
+ self.k_g[k],
338
+ R_fp,
339
+ J=self.J,
340
+ )[1][0]
341
+
342
+ # Delta-circuit thermal resistances
343
+ Rd = np.zeros((2, 2))
344
+ Rd[self._iInner, self._iInner] = np.inf
345
+ Rd[self._iInner, self._iOuter] = R_ff
346
+ Rd[self._iOuter, self._iInner] = R_ff
347
+ Rd[self._iOuter, self._iOuter] = R_fg
348
+ self.Rd.append(Rd)
349
+
350
+ if initialize_stored_coeff:
351
+ self.coaxial._initialize_stored_coefficients()
352
+
353
+ def get_temperature_depthvar(
354
+ self,
355
+ T_f_in: float,
356
+ signpower: float,
357
+ Rs: np.ndarray,
358
+ soil_props: SoilProperties,
359
+ nsegments: int = 10,
360
+ ) -> tuple:
361
+ """
362
+ Compute outlet temperature and thermal performance for depth-variable borehole.
363
+
364
+ This method partitions the borehole depth into segments and iteratively computes
365
+ fluid temperatures, borehole temperatures, and heat flows based on thermal
366
+ resistances and soil properties.
367
+
368
+ Parameters
369
+ ----------
370
+ T_f_in : float
371
+ Inlet fluid temperature (°C).
372
+ signpower : float
373
+ Sign and magnitude for initial power guess.
374
+ Rs : np.ndarray
375
+ Array of thermal resistances for each segment.
376
+ soil_props : SoilProperties
377
+ Object defining soil properties and temperature gradient.
378
+ nsegments : int, optional
379
+ Number of depth segments. Default is 10.
380
+
381
+ Returns
382
+ -------
383
+ T_f_out : float
384
+ Outlet fluid temperature (°C).
385
+ power : float
386
+ Heat extraction or injection power (W).
387
+ Reff : float
388
+ Effective thermal resistance (m·K/W).
389
+ ptemp : np.ndarray
390
+ Fluid temperatures per segment.
391
+ Tb : np.ndarray
392
+ Borehole temperatures per segment.
393
+ qbz : np.ndarray
394
+ Heat flow per segment (W).
395
+ """
396
+ bh = self.b
397
+ hstep = bh.H / nsegments
398
+
399
+ # Depth coordinates (mid of each segment)
400
+ zmin = bh.D + 0.5 * hstep
401
+ zmax = bh.D + bh.H - 0.5 * hstep
402
+ zseg = np.linspace(zmin, zmax, nsegments)
403
+
404
+ # soil temperature at each depth segment
405
+ Tg_borehole = soil_props.getTg(zseg)
406
+
407
+ # initalize arrays
408
+ qbz = Tg_borehole * 0.0
409
+ Tb = np.arange(nsegments, dtype=float)
410
+ Tf = Tb * 0.0
411
+
412
+ ptemp = np.arange((nsegments + 1) * self.nPipes, dtype=float).reshape(
413
+ nsegments + 1, self.nPipes
414
+ )
415
+
416
+ # initialize the top temperatures (inlet)
417
+ ptemp[0, : self.nInlets] = T_f_in
418
+
419
+ # iterate for T_f_out such that bottom temperatures become the same
420
+ dtfout = 0
421
+ dtempold = 0
422
+ dtempnew = 1
423
+ T_f_out = T_f_in + 1e-1 * signpower
424
+ cont = True
425
+
426
+ while abs(dtempnew) > 1e-4 or cont:
427
+ # set outlet boundary condition guess
428
+ ptemp[0, self.nInlets :] = T_f_out
429
+
430
+ # loop over each segment
431
+ for i in range(nsegments):
432
+ Rd = self._Rd[i]
433
+ b = 1.0 / Rd[0][0]
434
+
435
+ # Tf is only for output purposes
436
+ Tf[i] = np.average(ptemp[i])
437
+
438
+ # prep next segment's temperatures
439
+ ptemp[i + 1] = ptemp[i]
440
+
441
+ # iteration variables within the segment
442
+ tseg = 0.5 * (ptemp[i] + ptemp[i + 1])
443
+ dtemp = 1
444
+ icount = 0
445
+
446
+ # check that Rs[i]*b is not close to 1 if so modify with small number
447
+ if Rs[i] < 0:
448
+ Rs[i] = abs(Rs[i])
449
+
450
+ # iterate until pipe temperatures converge
451
+ while np.dot(dtemp, dtemp) > 1e-10:
452
+ icount += 1
453
+ tsegold = tseg
454
+
455
+ a = b * tseg[0]
456
+ Tb[i] = (Rs[i] * a + Tg_borehole[i]) / (1 + Rs[i] * b)
457
+
458
+ q1 = b * (tseg[0] - Tb[i])
459
+ q2 = (tseg[1] - tseg[0]) / Rd[0][1]
460
+
461
+ # Update fluid temperatures
462
+ mflow0 = self.m_flow_pipe[0] * self.scaleflow * self.cp_f
463
+ mflow1 = self.m_flow_pipe[1] * self.scaleflow * self.cp_f
464
+
465
+ ptemp[i + 1][0] = ptemp[i][0] - q1 * hstep / mflow0
466
+ ptemp[i + 1][0] += q2 * hstep / mflow0
467
+
468
+ ptemp[i + 1][1] = ptemp[i][1] - q2 * hstep / mflow1
469
+
470
+ tseg = 0.5 * (ptemp[i] + ptemp[i + 1])
471
+ dtemp = tseg - tsegold
472
+
473
+ # segment heat flow
474
+ qbz[i] = -q1 * hstep
475
+
476
+ # compute temperature mismatch for iteration
477
+ dtempnew = np.sum(
478
+ ptemp[nsegments, self.nInlets :] * self.m_flow_pipe[self.nInlets :]
479
+ ) + np.sum(
480
+ ptemp[nsegments, : self.nInlets] * self.m_flow_pipe[: self.nInlets]
481
+ )
482
+
483
+ # Update outlet temperature guess
484
+ if abs(dtfout) > 0:
485
+ g = dtfout / (dtempnew - dtempold)
486
+ dtfout = -dtempnew * g
487
+ cont = False
488
+ else:
489
+ dtfout = 1e-1
490
+ cont = True
491
+
492
+ dtempold = dtempnew
493
+ T_f_out += dtfout
494
+
495
+ # Final power and effective resistance
496
+ power = (T_f_out - T_f_in) * self.m_flow * self.scaleflow * self.cp_f
497
+
498
+ if abs(np.sum(qbz) - power) > 1:
499
+ print("power is not sumq (sumq, power):", np.sum(qbz), ",", power)
500
+
501
+ Reff = -np.average(Tf - Tb) * bh.H / power
502
+
503
+ return T_f_out, power, Reff, ptemp, Tb, qbz