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