Verleyen et al. (2022)

The code below is used in the article of Verleyen et al. (2022) 1.

  1"""
  2This document contains all the scripts for the figures of Verleyen et al. (2022).
  3One needs GHEtool version 2.0.6 to run this code.   
  4"""
  5import math
  6
  7import matplotlib.pyplot as plt
  8import numpy as np
  9
 10# code for making figures black-and-white ready
 11from cycler import cycler
 12
 13from GHEtool import Borefield, GroundData
 14
 15color_c = cycler('color', ['k'])
 16style_c = cycler('linestyle', ['-', '--', ':', '-.'])
 17markr_c = cycler('marker', ['', '.', 'o'])
 18c_cms = color_c * markr_c * style_c
 19c_csm = color_c * style_c * markr_c
 20plt.rc('axes', prop_cycle=c_cms)
 21plt.rcParams['lines.markersize'] = 8
 22
 23
 24def figure_1():
 25    """
 26    This function generates the first figure (part a/b) of the article.
 27    """
 28
 29    # initiate ground data
 30    ground_data = GroundData(2.4, 10, 0.12)
 31
 32    # initiate borefield model
 33    borefield = Borefield()
 34    borefield.set_ground_parameters(ground_data)
 35    borefield.create_rectangular_borefield(10, 10, 6, 6, 100, 4)
 36
 37    # initiate depth array
 38    depth = 150
 39
 40    # dimensionless time
 41    ts = 150**2 / (9 * ground_data.k_s)
 42
 43    # time array
 44    nb_of_timesteps = 50
 45    time_dimensionless = np.linspace(2, 14, nb_of_timesteps)
 46
 47    # convert to seconds
 48    time_in_seconds = np.exp(time_dimensionless) * ts
 49
 50    # calculate g-functions
 51    result = np.zeros(nb_of_timesteps)
 52    result = borefield.gfunction(time_in_seconds, depth)
 53
 54    # create figure
 55    fig, axs = plt.subplots(1, 2, figsize=(10, 3), constrained_layout=True)
 56
 57    # create figure g-function (lin)
 58    # plot g-functions
 59    axs[0].plot(time_in_seconds/8760/3600, result / (2 * math.pi * 2.4))
 60
 61    # layout
 62    axs[0].set_title("Step response when applying a constant heat injection")
 63    axs[0].set_xlabel("Time (years)")
 64    axs[0].set_ylabel("Temperature difference (K)")
 65    axs[0].set_ylim(0, 5)
 66    axs[0].set_xlim(0, 40)
 67
 68    # create figure g-function (semi-log)
 69    # plot g-functions
 70    axs[1].plot(time_dimensionless, result)
 71
 72    # layout
 73    axs[1].set_title("Equivalent g-function for the step response")
 74    axs[1].set_xlabel("ln(t/ts)")
 75    axs[1].set_ylabel("g-function value")
 76    axs[1].set_ylim(-2, 60)
 77
 78    # plt.legend()
 79    plt.show()
 80
 81
 82def figure_2():
 83    """
 84    This function generates the second figure of the article.
 85    """
 86
 87    # initiate ground data
 88    ground_data = GroundData(2.4, 10, 0.12)
 89
 90    # initiate borefield model
 91    borefield = Borefield()
 92    borefield.set_ground_parameters(ground_data)
 93    borefield.create_rectangular_borefield(10, 10, 6, 6, 100, 4)
 94
 95    # initiate depth array
 96    depths = np.array([25, 50, 100, 150, 200])
 97
 98    # dimensionless time
 99    ts = 150**2 / (9 * ground_data.k_s)
100
101    # time array
102    nb_of_timesteps = 50
103    time_dimensionless = np.linspace(2, 14, nb_of_timesteps)
104
105    # convert to seconds
106    time_in_seconds = np.exp(time_dimensionless) * ts
107
108    # calculate g-functions
109    results = np.zeros((5, nb_of_timesteps))
110    for i in range(5):
111        results[i] = borefield.gfunction(time_in_seconds, depths[i])
112
113    # create figure
114    plt.figure()
115
116    # plot g-functions
117    plt.plot(time_dimensionless, results[0], label="25m")
118    plt.plot(time_dimensionless, results[1], label="50m")
119    plt.plot(time_dimensionless, results[2], label="100m")
120    plt.plot(time_dimensionless, results[3], label="150m")
121    plt.plot(time_dimensionless, results[4], label="200m")
122
123    # plot lines for Ra
124    line1 = math.log(6*3600/ts)
125    line2 = math.log((20 * 8760 + 730) * 3600 / ts)
126    plt.vlines(line1, ymin=-5, ymax=5, colors="black", lw=0.75, ls="--")
127    plt.vlines(line2, ymin=-5, ymax=50, colors="black", lw=0.75, ls="--")
128
129    plt.annotate('', xy=(line1, -0.4), xytext=(line2, -0.4),
130                 arrowprops=dict(arrowstyle='<->', color='black'))
131    plt.text((line1+line2)/2, 0.2, "Ra", horizontalalignment='center')
132
133    # layout
134    # plt.title("G-function values for different borefield depths")
135    plt.xlabel("ln(t/ts)")
136    plt.ylabel("g-function value")
137    plt.ylim(-2, 60)
138
139    plt.legend()
140    plt.show()
141
142
143def figure_3():
144    """
145    This function creates the third figure of the article.
146    """
147
148    # initiate borefield model
149    borefield = Borefield()
150
151    # initiate depth for evaluations
152    nb_depths = 20
153    depth_array = np.linspace(50, 350, nb_depths)
154
155    # initiate list of borefield configurations
156    configs = [(10, 10), (11, 11), (12, 12), (14, 14),
157               (15, 15), (18, 18), (20, 20)]
158
159    results = []
160
161    for n1, n2 in configs:
162        depths = []
163        for H in depth_array:
164            # set ground data
165            ground_data = GroundData(2.4, 10, 0.12)
166
167            # set ground data
168            borefield.set_ground_parameters(ground_data)
169
170            # set borefield
171            borefield.create_rectangular_borefield(n1, n2, 7, 7, H, 4)
172
173            # calculate gfunction
174            gfunction = borefield.gfunction(borefield.time, H)
175
176            # calculate Ra
177            Ra = (gfunction[2] - gfunction[1]) / (2 * math.pi * ground_data.k_s)
178
179            # add to depths
180            depths.append(Ra)
181
182        # add to results
183        results.append(depths)
184
185    # create figure
186    plt.figure()
187    for i, config in enumerate(configs):
188        plt.plot(depth_array, results[i], label=str(config[0]) + "x" + str(config[1]))
189
190    # plt.title("Ra for different borefield configurations")
191    plt.xlabel("Depth (m)")
192    plt.ylabel("Ra (mK/W)")
193
194    plt.legend()
195    plt.show()
196
197
198def figure_4():
199    """
200    This code creates the fourth figure of the article
201    """
202    # initiate borefield
203    borefield = Borefield()
204
205    # set the correct sizing method
206    borefield.sizing_setup(L2_sizing=True)
207
208    # initiate array with imbalances
209    imbalance_array = np.linspace(200, 1600, 20)
210
211    # initiate list of borefield configurations
212    configs = [(10, 10), (11, 11), (12, 12), (14, 14),
213               (15, 15), (18, 18), (20, 20)]
214
215    # initiate loads
216    monthly_load_heating_percentage = np.array([0.155, 0.148, 0.125, .099, .064, 0., 0., 0., 0.061, 0.087, .117, 0.144])
217    monthly_load_cooling_percentage = np.array([0.025, 0.05, 0.05, .05, .075, .1, .2, .2, .1, .075, .05, .025])
218    monthly_load_heating = monthly_load_heating_percentage * 100 * 10 ** 3  # kWh
219    monthly_load_cooling_init = monthly_load_cooling_percentage * 100 * 10 ** 3  # kWh
220    peak_cooling_init = np.array([0., 0, 34., 69., 133., 187., 213., 240., 160., 37., 0., 0.])  # Peak cooling in kW
221    peak_heating = np.array([160., 142, 102., 55., 0., 0., 0., 0., 40.4, 85., 119., 136.])
222
223    # set heating loads
224    borefield.set_peak_heating(peak_heating)
225    borefield.set_baseload_heating(monthly_load_heating)
226
227    results = []
228    for i, config in enumerate(configs):
229        depth_array = []
230        for imbalance in imbalance_array:
231            # initiate ground data
232            ground_data = GroundData(2.4, 10, 0.12)
233
234            # set ground data
235            borefield.set_ground_parameters(ground_data)
236
237            # set borefield
238            borefield.create_rectangular_borefield(config[0], config[1], 7, 7, 100, 4)
239
240            # calculate loads
241            extra_load = imbalance / 12 * 10 ** 3  # kWh
242            monthly_load_cooling = monthly_load_cooling_init + extra_load
243            peak_cooling = peak_cooling_init + extra_load / 730
244
245            # set cooling loads
246            borefield.set_peak_cooling(peak_cooling)
247            borefield.set_baseload_cooling(monthly_load_cooling)
248            try:
249                depth = borefield.size()
250                depth_array.append(depth)
251            except:
252                pass
253
254        results.append(depth_array)
255
256    # create figure
257    plt.figure()
258    for i, config in enumerate(configs):
259        plt.plot(imbalance_array[:len(results[i])], results[i], label=str(config[0]) + "x" + str(config[1]))
260
261    # plt.title("Depth for different imbalances")
262    plt.xlabel("Imbalance (MWh/y)")
263    plt.ylabel("Depth (m)")
264
265    plt.legend()
266    plt.show()
267
268
269def figure_5():
270    """
271    This function creates the fifth figure of the article.
272    """
273
274    # initiate borefield
275    borefield = Borefield()
276
277    # set the correct sizing method
278    borefield.sizing_setup(L2_sizing=True)
279
280    # initiate array with imbalances
281    imbalance_array = np.linspace(200, 1600, 20)
282
283    # initiate list of borefield configurations
284    configs = [(10, 10), (11, 11), (12, 12), (14, 14),
285               (15, 15), (18, 18), (20, 20)]
286
287    # initiate loads
288    monthly_load_heating_percentage = np.array([0.155, 0.148, 0.125, .099, .064, 0., 0., 0., 0.061, 0.087, .117, 0.144])
289    monthly_load_cooling_percentage = np.array([0.025, 0.05, 0.05, .05, .075, .1, .2, .2, .1, .075, .05, .025])
290    monthly_load_heating = monthly_load_heating_percentage * 100 * 10 ** 3  # kWh
291    monthly_load_cooling_init = monthly_load_cooling_percentage * 100 * 10 ** 3  # kWh
292    peak_cooling_init = np.array([0., 0, 34., 69., 133., 187., 213., 240., 160., 37., 0., 0.])  # Peak cooling in kW
293    peak_heating = np.array([160., 142, 102., 55., 0., 0., 0., 0., 40.4, 85., 119., 136.])
294
295    # set heating loads
296    borefield.set_peak_heating(peak_heating)
297    borefield.set_baseload_heating(monthly_load_heating)
298
299    results = []
300    for i, config in enumerate(configs):
301        Ra_array = []
302        for imbalance in imbalance_array:
303            # initiate ground data
304            ground_data = GroundData(2.4, 10, 0.12)
305
306            # set ground data
307            borefield.set_ground_parameters(ground_data)
308
309            # set borefield
310            borefield.create_rectangular_borefield(config[0], config[1], 7, 7, 100, 4)
311
312            # calculate loads
313            extra_load = imbalance / 12 * 10 ** 3  # kWh
314            monthly_load_cooling = monthly_load_cooling_init + extra_load
315            peak_cooling = peak_cooling_init + extra_load / 730
316
317            # set cooling loads
318            borefield.set_peak_cooling(peak_cooling)
319            borefield.set_baseload_cooling(monthly_load_cooling)
320            try:
321                depth = borefield.size()
322
323                # calculate gfunction
324                gfunction = borefield.gfunction(borefield.time, depth)
325
326                # calculate Ra
327                Ra = (gfunction[2] - gfunction[1]) / (2 * math.pi * borefield.k_s)
328
329                Ra_array.append(Ra)
330            except:
331                pass
332
333        results.append(Ra_array)
334
335    # create figure
336    plt.figure()
337    for i, config in enumerate(configs):
338        plt.plot(imbalance_array[:len(results[i])], results[i], label=str(config[0]) + "x" + str(config[1]))
339
340    # plt.title("Ra for different borefield configurations")
341    plt.xlabel("Imbalance (MWh/y)")
342    plt.ylabel("Ra (mK/W)")
343
344    plt.legend()
345    plt.show()
346
347
348def figure_7():
349    """
350    This function creates the seventh figure in the article.
351    """
352
353    # initiate borefield
354    borefield = Borefield()
355
356    # set the correct sizing method
357    borefield.sizing_setup(L2_sizing=True)
358
359    # initiate array with imbalances
360    imbalance_array = np.linspace(100, 500, 10)
361
362    # initiate list of borefield configurations
363    configs = [(7, 15), (15, 15)]
364
365    # initiate loads
366    monthly_load_heating_percentage = np.array([0.155, 0.148, 0.125, .099, .064, 0., 0., 0., 0.061, 0.087, .117, 0.144])
367    monthly_load_cooling_percentage = np.array([0.025, 0.05, 0.05, .05, .075, .1, .2, .2, .1, .075, .05, .025])
368    monthly_load_heating = monthly_load_heating_percentage * 100 * 10 ** 3  # kWh
369    monthly_load_cooling_init = monthly_load_cooling_percentage * 100 * 10 ** 3  # kWh
370    peak_cooling_init = np.array([0., 0, 34., 69., 133., 187., 213., 240., 160., 37., 0., 0.])  # Peak cooling in kW
371    peak_heating = np.array([160., 142, 102., 55., 0., 0., 0., 0., 40.4, 85., 119., 136.])
372
373    # set heating loads
374    borefield.set_peak_heating(peak_heating)
375    borefield.set_baseload_heating(monthly_load_heating)
376
377    results = []
378    for i, config in enumerate(configs):
379        Ra_array = []
380        for imbalance in imbalance_array:
381            # initiate ground data
382            ground_data = GroundData(2.4, 10, 0.12)
383
384            # set ground data
385            borefield.set_ground_parameters(ground_data)
386
387            # set borefield
388            borefield.create_rectangular_borefield(config[0], config[1], 7, 7, 100, 4)
389
390            # calculate loads
391            extra_load = imbalance / 12 * 10 ** 3  # kWh
392            monthly_load_cooling = monthly_load_cooling_init + extra_load
393            peak_cooling = peak_cooling_init + extra_load / 730
394
395            # set cooling loads
396            borefield.set_peak_cooling(peak_cooling)
397            borefield.set_baseload_cooling(monthly_load_cooling)
398            try:
399                depth = borefield.size()
400
401                # calculate gfunction
402                gfunction = borefield.gfunction(borefield.time, depth)
403
404                # calculate Ra
405                Ra = (gfunction[2] - gfunction[1]) / (2 * math.pi * borefield.k_s)
406
407                Ra_array.append(Ra)
408            except:
409                pass
410
411        results.append(Ra_array)
412
413    # create figure
414    plt.figure()
415    for i, config in enumerate(configs):
416        plt.plot(imbalance_array[:len(results[i])], results[i], label=str(config[0]) + "x" + str(config[1]))
417
418    # plt.title("Ra for different borefield configurations")
419    plt.xlabel("Imbalance (MWh/y)")
420    plt.ylabel("Ra (mK/W)")
421
422    plt.annotate('', xy=(300, 2.167), xytext=(350, 2.205),
423                 arrowprops=dict(arrowstyle='<-', color='black'))
424    plt.annotate('', xy=(280, 2.15), xytext=(240, 2.02),
425                 arrowprops=dict(arrowstyle='<-', color='black'))
426
427    plt.legend()
428    plt.show()
429
430
431def figure_8():
432    """
433    This function creates the eight figure of the article.
434    """
435
436    # initiate borefield
437    borefield1 = Borefield()
438    borefield2 = Borefield()
439
440    # set the correct sizing method
441    borefield1.sizing_setup(L2_sizing=True)
442    borefield2.sizing_setup(L2_sizing=True)
443
444    # initiate array with imbalances percentages
445    imbalance_array = np.linspace(30, 70, 20)
446
447    # initiate list of borefield configurations
448    configs = [((20, 6), (20, 6)),
449               ((18, 8), (16, 6)),
450               ((14, 12), (18, 4)),
451               ((16, 12), (16, 5))]
452
453    # initiate imbalance
454    imbalance = 800
455
456    # initiate loads
457    monthly_load_heating_percentage = np.array([0.155, 0.148, 0.125, .099, .064, 0., 0., 0., 0.061, 0.087, 0.117, 0.144])
458    monthly_load_cooling_percentage = np.array([0.025, 0.05, 0.05, .05, .075, .1, .2, .2, .1, .075, .05, .025])
459    monthly_load_heating = monthly_load_heating_percentage * (100 + imbalance) * 10 ** 3
460    monthly_load_cooling = monthly_load_cooling_percentage * 100 * 10 ** 3  # kWh
461    peak_cooling = np.array([0., 0., 22., 44., 83., 117., 134., 150., 100., 23., 0., 0.])
462    peak_heating = np.array([300., 268., 191., 103., 75., 0., 0., 38., 76., 160., 224., 255.])
463
464    results = []
465    for i, config_pair in enumerate(configs):
466        config1, config2 = config_pair
467        ratio_of_nb_of_boreholes = config1[0] * config1[1] / (config2[0] * config2[0] + config1[0] * config1[1])
468
469        # initiate ground data
470        ground_data1 = GroundData(2.4, 10, 0.14)
471        ground_data2 = GroundData(2.4, 10, 0.14)
472
473        # set ground data
474        borefield1.set_ground_parameters(ground_data1)
475        borefield2.set_ground_parameters(ground_data2)
476
477        # set borefields
478        borefield1.create_rectangular_borefield(config1[0], config1[1], 7, 7, 100, 4)
479        borefield2.create_rectangular_borefield(config2[0], config2[1], 7, 7, 100, 4)
480
481        # set cooling peak according to the ratio of nb_of_boreholes
482        borefield1.set_peak_cooling(peak_cooling * ratio_of_nb_of_boreholes)
483        borefield2.set_peak_cooling(peak_cooling * (1-ratio_of_nb_of_boreholes))
484
485        results_temp = []
486
487        for imbalance_percentage in imbalance_array:
488
489            # set the imbalance loads
490            borefield1.set_baseload_heating(monthly_load_heating * imbalance_percentage/100)
491            borefield2.set_baseload_heating(monthly_load_heating * (100 - imbalance_percentage)/100)
492
493            # set peak load heating
494            borefield1.set_peak_heating((peak_heating + imbalance_percentage/100 * imbalance / 12 / 730 * 10 ** 3))
495            borefield2.set_peak_heating((peak_heating + (100 - imbalance_percentage) / 100 * imbalance / 12 / 730 * 10 ** 3))
496
497            # set baseload cooling equally over the fields
498            borefield1.set_baseload_cooling(monthly_load_cooling * imbalance_percentage/100)
499            borefield2.set_baseload_cooling(monthly_load_cooling * (100 - imbalance_percentage)/100)
500
501            try:
502                depth1 = borefield1.size()
503                depth2 = borefield2.size()
504
505                results_temp.append(depth1 * config1[0] * config1[1] + depth2 * config2[0] * config2[1])
506            except:
507                results_temp.append(0)
508
509        results.append(results_temp)
510
511    # create figure
512    plt.figure()
513    for i, config in enumerate(configs):
514        plt.plot(imbalance_array[:len(results[i])], results[i], label=str(config[0]) + "x" + str(config[1]))
515
516    # plt.title("Effect of imbalance distribution on total borefield length")
517    plt.xlabel("Percentage of imbalance on field with largest number of boreholes")
518    plt.ylabel("Total borefield length (m)")
519
520    plt.legend()
521    plt.show()
522
523
524if __name__ == "__main__":
525    figure_1()
526    figure_2()
527    figure_3()
528    figure_4()
529    figure_5()
530    figure_7()
531    figure_8()

References

1

Verleyen, L., Peere, W., Michiels, E., Boydens, W., Helsen, L. (2022). The beauty of reason and insight: a story about 30 years old borefield equations. IEA HPT Magazine 40(3), 36-39, https://doi.org/10.23697/6q4n-3223