Simulation of fluid temperatures in a field of multiple boreholes¶
This example demonstrates the use of the networks module to predict the fluid temperature variations in a bore field with known heat extraction rates.
The g-function of a bore field is first calculated using the equal inlet fluid temperature boundary condition [1]. Then, the borehole wall temperature variations are calculated using the load aggregation scheme of Claesson and Javed [2]. The time-variation of heat extraction rates is given by the synthetic load profile of Bernier et al. [3]. Predicted inlet and outlet fluid temperatures of double U-tube boreholes are calculated using the model of Cimmino [4].
The script is located in: pygfunction/examples/fluid_temperature_multiple_boreholes.py
1# -*- coding: utf-8 -*-
2""" Example of simulation of a geothermal system with multiple boreholes.
3
4 The g-function of a bore field is calculated for boundary condition of
5 mixed inlet fluid temperature into the boreholes. Then, the borehole
6 wall temperature variations resulting from a time-varying load profile
7 are simulated using the aggregation method of Claesson and Javed (2012).
8 Predicted outlet fluid temperatures of double U-tube borehole are
9 evaluated.
10
11"""
12import matplotlib.pyplot as plt
13import numpy as np
14from scipy.constants import pi
15
16import pygfunction as gt
17
18
19def main():
20 # -------------------------------------------------------------------------
21 # Simulation parameters
22 # -------------------------------------------------------------------------
23
24 # Borehole dimensions
25 D = 4.0 # Borehole buried depth (m)
26 H = 150.0 # Borehole length (m)
27 r_b = 0.075 # Borehole radius (m)
28
29 # Bore field geometry (rectangular array)
30 N_1 = 6 # Number of boreholes in the x-direction (columns)
31 N_2 = 4 # Number of boreholes in the y-direction (rows)
32 B = 7.5 # Borehole spacing, in both directions (m)
33
34 # Pipe dimensions
35 r_out = 0.0211 # Pipe outer radius (m)
36 r_in = 0.0147 # Pipe inner radius (m)
37 D_s = 0.052 # Shank spacing (m)
38 epsilon = 1.0e-6 # Pipe roughness (m)
39
40 # Pipe positions
41 # Double U-tube [(x_in1, y_in1), (x_in2, y_in2),
42 # (x_out1, y_out1), (x_out2, y_out2)]
43 pos = [(-D_s, 0.), (0., -D_s), (D_s, 0.), (0., D_s)]
44
45 # Ground properties
46 alpha = 1.0e-6 # Ground thermal diffusivity (m2/s)
47 k_s = 2.0 # Ground thermal conductivity (W/m.K)
48 T_g = 10.0 # Undisturbed ground temperature (degC)
49
50 # Grout properties
51 k_g = 1.0 # Grout thermal conductivity (W/m.K)
52
53 # Pipe properties
54 k_p = 0.4 # Pipe thermal conductivity (W/m.K)
55
56 # Fluid properties
57 m_flow_borehole = 0.25 # Total fluid mass flow rate per borehole (kg/s)
58 m_flow_network = m_flow_borehole*N_1*N_2 # Total fluid mass flow rate (kg/s)
59 # The fluid is propylene-glycol (20 %) at 20 degC
60 fluid = gt.media.Fluid('MPG', 20.)
61 cp_f = fluid.cp # Fluid specific isobaric heat capacity (J/kg.K)
62 rho_f = fluid.rho # Fluid density (kg/m3)
63 mu_f = fluid.mu # Fluid dynamic viscosity (kg/m.s)
64 k_f = fluid.k # Fluid thermal conductivity (W/m.K)
65
66 # g-Function calculation options
67 options = {'nSegments': 8,
68 'disp': True}
69
70 # Simulation parameters
71 dt = 3600. # Time step (s)
72 tmax = 1.*8760. * 3600. # Maximum time (s)
73 Nt = int(np.ceil(tmax/dt)) # Number of time steps
74 time = dt * np.arange(1, Nt+1)
75
76 # Load aggregation scheme
77 LoadAgg = gt.load_aggregation.ClaessonJaved(dt, tmax)
78
79 # -------------------------------------------------------------------------
80 # Initialize bore field and pipe models
81 # -------------------------------------------------------------------------
82
83 # The field is a retangular array
84 boreField = gt.boreholes.rectangle_field(N_1, N_2, B, B, H, D, r_b)
85 nBoreholes = len(boreField)
86
87 # Pipe thermal resistance
88 R_p = gt.pipes.conduction_thermal_resistance_circular_pipe(
89 r_in, r_out, k_p)
90
91 # Fluid to inner pipe wall thermal resistance (Double U-tube in parallel)
92 m_flow_pipe = m_flow_borehole/2
93 h_f = gt.pipes.convective_heat_transfer_coefficient_circular_pipe(
94 m_flow_pipe, r_in, mu_f, rho_f, k_f, cp_f, epsilon)
95 R_f = 1.0/(h_f*2*pi*r_in)
96
97 # Double U-tube (parallel), same for all boreholes in the bore field
98 UTubes = []
99 for borehole in boreField:
100 UTube = gt.pipes.MultipleUTube(
101 pos, r_in, r_out, borehole, k_s, k_g, R_f + R_p,
102 nPipes=2, config='parallel')
103 UTubes.append(UTube)
104 # Build a network object from the list of UTubes
105 network = gt.networks.Network(
106 boreField, UTubes, m_flow_network=m_flow_network, cp_f=cp_f)
107
108 # -------------------------------------------------------------------------
109 # Calculate g-function
110 # -------------------------------------------------------------------------
111
112 # Get time values needed for g-function evaluation
113 time_req = LoadAgg.get_times_for_simulation()
114 # Calculate g-function
115 gFunc = gt.gfunction.gFunction(
116 network, alpha, time=time_req, boundary_condition='MIFT',
117 options=options)
118 # Initialize load aggregation scheme
119 LoadAgg.initialize(gFunc.gFunc/(2*pi*k_s))
120
121 # -------------------------------------------------------------------------
122 # Simulation
123 # -------------------------------------------------------------------------
124
125 # Evaluate heat extraction rate
126 Q_tot = nBoreholes*synthetic_load(time/3600.)
127
128 T_b = np.zeros(Nt)
129 T_f_in = np.zeros(Nt)
130 T_f_out = np.zeros(Nt)
131 for i, (t, Q_i) in enumerate(zip(time, Q_tot)):
132 # Increment time step by (1)
133 LoadAgg.next_time_step(t)
134
135 # Apply current load (in watts per meter of borehole)
136 Q_b = Q_i/nBoreholes
137 LoadAgg.set_current_load(Q_b/H)
138
139 # Evaluate borehole wall temperature
140 deltaT_b = LoadAgg.temporal_superposition()
141 T_b[i] = T_g - deltaT_b
142
143 # Evaluate inlet fluid temperature (all boreholes are the same)
144 T_f_in[i] = network.get_network_inlet_temperature(
145 Q_tot[i], T_b[i], m_flow_network, cp_f, nSegments=1)
146
147 # Evaluate outlet fluid temperature
148 T_f_out[i] = network.get_network_outlet_temperature(
149 T_f_in[i], T_b[i], m_flow_network, cp_f, nSegments=1)
150
151 # -------------------------------------------------------------------------
152 # Plot hourly heat extraction rates and temperatures
153 # -------------------------------------------------------------------------
154
155 # Configure figure and axes
156 fig = gt.utilities._initialize_figure()
157
158 ax1 = fig.add_subplot(211)
159 # Axis labels
160 ax1.set_xlabel(r'Time [hours]')
161 ax1.set_ylabel(r'Total heat extraction rate [W]')
162 gt.utilities._format_axes(ax1)
163
164 # Plot heat extraction rates
165 hours = np.arange(1, Nt+1) * dt / 3600.
166 ax1.plot(hours, Q_tot)
167
168 ax2 = fig.add_subplot(212)
169 # Axis labels
170 ax2.set_xlabel(r'Time [hours]')
171 ax2.set_ylabel(r'Temperature [degC]')
172 gt.utilities._format_axes(ax2)
173
174 # Plot temperatures
175 ax2.plot(hours, T_b, label='Borehole wall')
176 ax2.plot(hours, T_f_out, '-.',
177 label='Outlet, double U-tube (parallel)')
178 ax2.legend()
179
180 # Adjust to plot window
181 plt.tight_layout()
182
183 # -------------------------------------------------------------------------
184 # Plot fluid temperature profiles
185 # -------------------------------------------------------------------------
186
187 # Evaluate temperatures at nz evenly spaced depths along the borehole
188 # at the (it+1)-th time step
189 nz = 20
190 it = 8724
191 z = np.linspace(0., H, num=nz)
192 T_f = UTubes[0].get_temperature(
193 z, T_f_in[it], T_b[it], m_flow_borehole, cp_f)
194
195
196 # Configure figure and axes
197 fig = gt.utilities._initialize_figure()
198
199 ax3 = fig.add_subplot(111)
200 # Axis labels
201 ax3.set_xlabel(r'Temperature [degC]')
202 ax3.set_ylabel(r'Depth from borehole head [m]')
203 gt.utilities._format_axes(ax3)
204
205 # Plot temperatures
206 pltFlu = ax3.plot(T_f, z, 'b-', label='Fluid')
207 pltWal = ax3.plot(np.array([T_b[it], T_b[it]]), np.array([0., H]),
208 'k--', label='Borehole wall')
209 ax3.legend(handles=[pltFlu[0]]+pltWal)
210
211 # Reverse y-axes
212 ax3.set_ylim(ax3.get_ylim()[::-1])
213 # Adjust to plot window
214 plt.tight_layout()
215
216 return
217
218
219def synthetic_load(x):
220 """
221 Synthetic load profile of Bernier et al. (2004).
222
223 Returns load y (in watts) at time x (in hours).
224 """
225 A = 2000.0
226 B = 2190.0
227 C = 80.0
228 D = 2.0
229 E = 0.01
230 F = 0.0
231 G = 0.95
232
233 func = (168.0-C)/168.0
234 for i in [1, 2, 3]:
235 func += 1.0/(i*pi)*(np.cos(C*pi*i/84.0)-1.0) \
236 *(np.sin(pi*i/84.0*(x-B)))
237 func = func*A*np.sin(pi/12.0*(x-B)) \
238 *np.sin(pi/4380.0*(x-B))
239
240 y = func + (-1.0)**np.floor(D/8760.0*(x-B))*abs(func) \
241 + E*(-1.0)**np.floor(D/8760.0*(x-B))/np.sign(np.cos(D*pi/4380.0*(x-F))+G)
242 return -y
243
244
245# Main function
246if __name__ == '__main__':
247 main()
References