How to use the heatfuncs Python library to calculate groundwater flow from temperature data Dylan J. Irvine
[email protected] Last updated: January 15, 2015 Version number: 0.0.1
Introduction heatfuncs.py is a Python library that contains functions to calculate groundwater flow from temperature data. Curently heatfuncs.py includes the analytical solutions by Bredehoeft and Papadopulos (1965), Mansure and Reiter (1979), and Lu and Ge (1996). The goal of heatfuncs.py is to produce easy to use Python functions to promote the more widespread use of temperature data in hydrogeology. The version number 0.0.1 denotes the fact that the library is still in the testing phase, and may contain errors. If any errors are noticed, please contact me using the email address above. Python is a really great programming language. It’s pretty easy to pick up (and if you don’t code, I have an example of all the coding required to run some of the functions included at the end of this document). The other key benefit of Python is that it is free, so there are no license/funding issues.
Before we start heatfuncs.py is a small library of functions to analyse temperature data using the Python programming language. The heatfuncs.py library uses some functions which are only available in relatively recent versions of Python. For example, the library was written using Python version 2.7.6. The easiest way to obtain Python, relevant libraries and a nice GUI, is to install Python XY (which is made for scientific and engineering purposes). To install Python XY, download an installer, and follow the instructions at: https://code.google.com/p/pythonxy/wiki/Downloads As a guide, once you’ve installed Python, you will write scripts, use functions, etc. using the Spyder GUI.
1
Important! You have to run heatfuncs.py at least once(!) You may be better at using Python than me. So if there is a better way of using the heatfuncs.py library than my description below, please let me know. First, given that you’re reading this, I assume that you’ve downloaded the library. Save the files in a sensible location on your computer. And by sensible, I mean somewhere that you will remember. Python scripts to call functions from the heatfuncs.py library will need to be stored in the same location on your computer. To have the functions from the heatfuncs.py library available to use, open the heatfuncs.py file in Spyder (the GUI that comes with the Python XY package), and run in once (either by pressing the green ’play’ button, or pressing F5). Depending on how your Spyder GUI is set up, some text may appear on the console window. Either way, nothing much should happen. Now create a new python script in the same directory. Python differs from other high level languages like MATLAB, in that if you want to use a function, you have to import it from the library where it belongs. I usually just import the entire library, but you can also import selected functions. e.g. two ways of importing functions from the numpy library (scientific computing library) are below: from numpy import * from numpy import exp, sum i.e. the first option simply imports all of the numpy functions, and the second only imports the exp and sum functions. This over-thinking things for now. Once you’ve run the heatfuncs.py script once, the functions contained within it are ready to use. At the end of this document is a script (that should be saved in the same directory as the heatfuncs.py script) that will run the BPrun function, and produce an estimate of qz in m s−1
Some other Python quirks I personally find it really annoying, but Python starts counting from 0. So if you had a column vector of data (let’s say you had a column of data called T). If you want to print the first value from T, you would type: print T[0] The other relevant example, would be if your geothermal data were stored in a file with 2 2
columns. The first column containing depth, and the second containing temperature. You could load the file (let’s say it was called Tatiara.dat, and that it has 2 header lines) to a parameter called Tatdata (or whatever you would like to call it) with the following: Tatdata = loadtxt(’Tatiara.dat’, skiprows=2) Now your depth data would be stored as Tatdata[:,0], and your temperature data would be stored as Tatdata[:,1]. i.e. the : means all rows of data, and the 0 or 1, refers to the 1st or 2nd column. Alternatively, you could use the snippet of text below to load the data straight into arrays called z and T (to match the nomencalture used here) e.g.: z = loadtxt(’Tatiara.dat’, skiprows=2, usecols=(0,)) T = loadtxt(’Tatiara.dat’, skiprows=2, usecols=(1,)) One last point. . . Commands in Python are case sensitive. So T and t are different. There are other quirks, but this will do for now.
3
Parameter shorthand Below is a list of all of the parameters used in heatfuncs.py. All parameters use the units: kg, m, s, ◦ C, J and W. Symbol T z z0 zL
Code symbol Units ◦ T C z m z0 m zL m
T0 TL L qz qx ρw cw λ0 γ η
T0 TL L qest qx pw cw kb gam eta
C m−1 ◦ C m−1 m m s−1 m s−1 kg m−3 J kg−1◦ C −1 W m−1◦ C −1 ◦ C m−1 ◦ C m−1
P ex P ez δ
Pex Pez delta
-
◦
Explanation Temperature Depth Depth of top of geothermal profile for analysis Depth of bottom of geothermal profile for analysis Temperature at z0 Temperature at zL Distance between z0 and zL Estimate of qz or value of qz Value of qx Density of water Specific heat capacity of water Thermal conductivty of solid-fluid matrix Horizontal thermal gradient (user entered) Vertical thermal gradient (calculated internally from T0 , TL and L) Horizontal Peclet number Vertical Peclet number ( γη )( PP eexz )
4
Analytical solutions included in heatfuncs.py Note: The explanation for each solution is given in full. There will be repetition. Symbols used may vary from the original publication for consistency. Analytical solutions included in heatfuncs.py: • Bredehoeft and Papadopulos (1965) • Mansure and Reiter (1979) • Lu and Ge (1996) Future plans to add: • Taniguchi (1993)
Functions included in heatfuncs.py An example of calling a function in Python would be to assign the square root of a number to a variable. For example, you may want to assign the square root of 27 to the variable b. To do this, you could type: b = sqrt(27.0) In all of the examples below, the heatfuncs.py functions are assigned to the variable a. However, you can use whatever variable name that you like.
• Tzprep, Normalises T and z data. Removes any data above z0 and ignores all data after zL , has been reached. This function deals with the fact that temperature is typically recorded as a sensor is lowered down, and then raised up through a borehole. Example: a = Tzprep(T, z, z0, zL) or: a = Tzprep(T, z, 10.0, 61.0) e.g. where user has already loaded T and z, and z0 = 10.0m and zL = 61.0 m. • Tzplot, This function is essentially the Tzprep function, but with plotting capabilities. In 5
addition to T , z, z0 ,zL , user also enters the output file name (must be a .png file), the output file destination (e.g. r’C:\this\is\my\folder’), the dpi of the output file, whether the plots are to be side by side (’row’), or stacked on top of one another (’col’), the fontsize, and line colour. An example could be (where the path to the destination folder has been set to the parameter outfolder): a = Tzplot(T, z, z0, zL, ’Tatiara.png’, outfolder, 300, ’row’, 10, ’#0066FF’) An example of the output with the ’row’ option is below:
Fig 1:Plot on the left shows the orignal T − z plot from 0 to 80 m. On the right, the Temperature and data has been normalised to be used in either the Bredehoeft and Papadopulos (1965) or Lu and Ge (1996) methods. • BPmin, Not intended to be called directly by the user. Calculates the weighted sum of squared error (SSE) between observed and modelled normalised T using the Bredehoeft and Papadopulos (1965) method. This function is not intended to be used directly by the user. It is called by BPq (details below). See Advanced options to change the weighting of the SSE. • BPq, Not intended to be called directly by the user. Uses the Python minimize function to find the qz that minimises the objective function (in this case, the sum of squared errors between observed and modelled normalised temperature). • BPrun, Calls Tzprep, BPmin and BPq. Produces the qz value that minimises the sum of squared errors between observed normalised temperature. Setting the plotswitch option to 1 also outputs a graph of normalised z against normalised T . Example: a = BPrun(T, z, z0, zL, qest, pw, cw, kb, plotoption) 6
An example of the output figure from the BPrun function:
Fig 2: Example of the normalised T = (Tz − T0 )/(TL − T0 ) and normalised z = z/L data (blue) and the fitted Bredehoeft and Papadopulos (1965) function (red dash) for a hyporthetical example • LGmin, Not intended to be called directly by the user. Like BPmin, but for the Lu and Ge (1996) method. • LGq, Not intended to be called directly by the user. Like BPq, but for the Lu and Ge (1996) method. • LGrun, Notice the pattern here? Like BPrun, but for the Lu and Ge (1996) method. Example: a = LGrun(T, z, z0, zL, qest, qx, pw, cw, kb, gam, plotoption) • LGmindelta, Not intended to be called directly by the user. Like LGmin, but the user enters δ rather than estimates of qx , γ, η • LGqdetla, Not intended to be called directly by the user. Like LGq, but where, as above, the user enters δ. 7
• LGrundelta, Notice the pattern here? Like LGrun, but where, as above, the user enters δ. Example: a = LGrundelta(T, z, z0, zL, qest, pw, cw, kb, delta, plotoption) • MRrun, Runs the Mansure and Reiter (1979) derivative method. This function may require to be run until the appropiate range of data has been determined by the user. This can be done by selecting an appropriate depth range of data to fit the shallow (linear) part of the dT /dz vs T graph. Example: a = MRrun(T, z, z0, zL, kb, dzpref, zfitfrom, zfitto, pw, cw) An example of the output figure from the Mansure and Reiter (1979) method:
Fig 3: Example of the Mansure and Reiter (1979) derivative method. In blue is the dT /dz vs T data, and red dash is the fitted shallow (linear) part of the dT /dz vs T data. The slope of the red line is m used in Equation 11.
8
Theory Thermal conductivity Many of the solutions in heatfuncs.py include bulk (i.e. saturated porous media) thermal conductivity, which is denoted as λ0 , and has units of W m−1◦ C m−1 . λ0 is calculated using the thermal conductivity of the solid (λs ), the thermal conductivity of water (λw ) and porosity (θ) using one of the two approaches below: λ0 = λs1−θ λθw ,
(1)
λ0 = λs (1 − θ) + λw θ,
(2)
Equation 1 is a geometric approach, and Equation 2 is an arithmetic approach to calculating λ0 . The approach that you use is up to you. Typically, the value of λ0 is going to be an estimate anyway. As a guide, λw is approximately 0.6 W m−1◦ C m−1 . Sand (i.e. solid) has a relatively high thermal conductivity λs ≈ 8.0 W m−1◦ C m−1 . A typical range of λ0 would range between ≈ 1.6 to 4.5 W m−1◦ C m−1 .
Bredehoeft and Papadopulos (1965) solution The Bredehoeft and Papadopulos (1965) analytical solution is a 1D solution to calculate vertical fluid flow (Darcy flux, qz ). The solution is straightforward to use, and only requires a geothermal profile, and an estimate of bulk thermal conductivity (λ0 , W m−1◦ C −1 ). The Bredehoeft and Papadopulos (1965) solution can be written as: Tz − T0 exp(P ez (z/L) − 1 = , TL − T0 exp(P ez ) − 1
(3)
where Tz is a temperature (◦ C) at a depth z (m), T0 and TL ares the temperature boundary conditions for the top and bottom of the model respectively, and P ez is the thermal Peclet number: P ez =
ρ w cw q z L , λ0
(4)
where ρw is the density of water (kg m−3 , e.g. 1000.0), cw is the specific heat capacity of water (J kg−1◦ C−1 , e.g. 4186.0), qz is the vertical Darcy flux (m s−1 ), L is the distance 9
between the points for T0 and TL (here these points are referred to as z0 and zL ), and λ0 is the bulk thermal conductivity (W m−1◦ C−1 ). The Bredehoeft and Papadopulos (1965) method obtains a qz estimate by finding the qz that produces the best fit between the normalised T data (i.e. (Tz − T0 )/(TL − T0 )) from the measurements, and equation 1. The functions from heatfuncs.py find this qz value by minimising the sum of squared errors between the observed and modelled data.
Lu and Ge (1996) solution The Lu and Ge (1996) solution is a 2D extension of the Bredehoeft and Papadopulos (1965) solution. In addition to a geothermal profile and an estimate of bulk thermal conductivity (λ0 , W m−1◦ C −1 ), the Lu and Ge (1996) analytical solution also requires a horizontal thermal gradient (which requires either another geothermal profile, and the gradient is calculated, or the estimation of a horizontal thermal gradient). The Lu and Ge (1996) solution can be written as: exp(P ez (z/L) − 1 γ P ex Tz − T0 = + TL − T0 exp(P ez ) − 1 η P ez
!
exp(P ez (z/L) − 1 z − , exp(P ez ) − 1 L
(5)
where Tz is a temperature (◦ C) at a depth z (m), T0 and TL ares the temperature boundary conditions for the top and bottom of the model respectively, and P ex and P ez are thermal Peclet numbers (below), L is the distance (m) between T0 and TL , γ is the horizontal thermal gradient ( positive when heat flow is leftward, ◦ C m−1 , requiring another geothemal profile, or an estiamte of γ) and η is the vertical thermal gradient (positive when heat flow is upward, ◦ C m−1 , which can be determined from the geothermal profile of interest). The P ex and P ez numbers are: P ex =
ρ w cw q x L , λ0
(6)
P ez =
ρ w cw q z L , λ0
(7)
and,
ex Lu and Ge (1996) also suggest that the γP term can be combined into a single parameter ηP ez δ that includes parameters which are often unknown. Following this, the Lu and Ge (1996) equation can be written as:
!
Tz − T0 exp(P ez (z/L) − 1 exp(P ez (z/L) − 1 z = +δ − , TL − T0 exp(P ez ) − 1 exp(P ez ) − 1 L 10
(8)
where qx and qz are the Darcy flux (m s−1 ) in the horizontal and vertical directions, and λ0 is bulk thermal conductivity (λ0 , W m−1◦ C −1 ). heatfuncs.py provides approaches to use either form of the Lu and Ge (1996) method. Notice that the first term on the right hand side of either form of the Lu and Ge (1996) solution is the Bredehoeft and Papadopulos (1965) solution. In cases where there is no horizontal thermal gradient (γ), or no horizontal flow (qx ), the solution reduces the Bredehoeft and Papadopulos (1965) solution. An extension of this point is that the solution is not sensitive to the final term on the right hand side where the γ, or qx are small. The next point is important, hence it’s in red. The first form of the Lu and Ge (1996) solultion simultaneously requires the estimation of both qx and qz . The approach taken in heatfuncs.py is to fix the qx value, and estimate qz . This process can be repeated for several qx values to determine the sensitivity of the qz estimate to qx . Alternatively, the user can run the δ based function, and use several δ values. The Lu and Ge (1996)method obtains a qz estimate by finding the qz that produces the best fit between the normalised T data (i.e. (Tz − T0 )/(TL − T0 )) from the measurements, and equation 5. The functions from heatfuncs.py find this qz value by minimising the sum of squared errors between the observed and modelled data. This approach requires both an initial estimate of qz and a fixed value of qx . As the solution for qz depends on the value of qx it is recommended that the function be run with a range of qx values and/or horizontal thermal gradients (γ) if these values are unknown.
Mansure and Reiter (1979) solution Mansure and Reiter (1979) present a method of determining fluid flow from geotherms using derivatives of temperautre with depth. This method performs best using high precision (i.e. 0.0001 ◦ C) equipment. Assuming steady state, vertical and uniform flow, Mansure and Reiter (1979) show that:
d(dT /dz) =
cw ρ w qz dT, λ0
(9)
and that the slope (m) of the plot dT /dz vs. T yields:
m=
cw ρ w q z , λ0 11
(10)
which can be rearranged to obtain: qz =
mλ0 . cw ρ w
(11)
When purely vertical flow occurs (and the system is thermally homogeneous), the relationship between dT /dz and T will be linear. When multidimensional flows occurs, typically, the shallow data will be linear, and deeper data are non-linear. It is recommended to fit the shallow, linear part of the curve. To perform calculations of fluid flow using the Mansure and Reiter (1979) method, the heatfuncs.py library interpolates the geotherm onto a steady depth profile, as typically, geotherms are measured at a constant time (as the sensor is lowered down the borehole), rather than at a constant depth.
Avdanced options heatfuncs.py has a number of advanced settings that can be changed by changing the options and re-running heatfuncs.py enact changes. The optional changes that can be made to heatfuncs.py include: 1. Changing the weighting of sum of squared errors (e.g. this is used in the Lu and Ge (1996) method) 2. Changing the convergence criteria for the minimize function in the BPq, LGq, or LGqdelta functions
Change SSE weightings By default, heatfuncs.py applies a weighted sum of square error (SSE). In the BPq, LGq and LGqdelta functions, w = the weighting array. This array has the same length as the input data (i.e. z/L), and ranges from some value, to 1. The w array is set up using the Python linspace function (which linearly spaces values between a user defined start (first value in function), and end (second value in function). The length of the array is the third value in the function). If the user desires to have no weightings, then the first value in the linspace function that sets up the w array can be set to one. Increasing the weight of the shallow data can be achieved by increasing the first value. Similarly, weighting the shallow data can be achieved by increasing the value of the end value (i.e. second number in the linspace function).
Reduce convergence criteria 12
The convergence criteria (xtol) are set to a very small value in the BPq, LGq and LGqdelta functions (these functions are used to minimize the SSE). By default, the convergence criteria are set to 1 ×10 −18 . This value can be increased/decreased by the user by changing the xtol value.
Detailed description of key functions In Spyder (a Python GUI), help on all functions can be obtained by typing the function name, followed by an open bracket. Detailed description of input and output of the function will appear in the Object inspector. Below are descriptions of two key functions:
Tzprep function This function is used to prepare raw data to be analysed in the Bredehoeft and Papadopulos (1965) solution (also used in the Lu and Ge (1996) related functions). The user enters the temperature data (T), depth data (z), the the depths of the top (z0) and bottom (zL) boundary conditions for the Bredehoeft and Papadopulos (1965) or Lu and Ge (1996) solutions. Geothermal profiles are recorded as the sensor is lowered down a borehole, and as it is raised up again. The Tzprep function overcomes this issue by using the first instance of the sensor equalling or exceeding either z0 or zL values. For zL this means that any data after the depth zL is ignored. The user does not necessarily have to call this function (as it is called by other BP related functions). However, the Tzprep function is a simple way to produce normalised T and z data (e.g. Tz − T0 /TL − T0 , or z/L). An example of calling Tzprep (where T and z have been read in) would be: a = Tzprep(T, z, z0, zL) Tzprep outputs a single variable (in this case ’a’) which contains position 0 = Tnorm, position 1 = znorm, position 2 = Ttrunc, position 3 = ztrunc, position 4 = T0, postion 5 = TL, position 6 = L. i.e. to print the normalised T data, the user could type: print a[0] or to plot the normalised T-z profile (i.e. z/L vs. (Tz − T0 /TL − T0 )), the user could type: 13
plot(a[0], a[1]) gca().invert yaxis() # Inverts the y axis to have 0 at the top. show() # The show command may or may not be required to actually show the plot
BPrun function This function calcualtes the qz (m s−1 ) that best fits the observed geothermal profile. Geothermal logs typically record temperature both as the sensor is lowered down the borehole, and as it is raised back up. This is overcome within the BPrun function, where the first depth that exceeds zL is used, and data deeper than this depth is ignored. Input for the function is: • T, 1D column of temperature data (◦ C) • z, 1D column the of depths corresponding to temperature data (m) • z0, Depth for upper boundary condition (m) • zL, Depth for lower boundary condition (m) • pw, Density of water (kg m−1 ) • cw, Specific heat capacity of water (J kg−1◦ C−1 ) • kb, Bulk thermal conductivity (W m−1◦ C−1 ) • qest, Estimate of Darcy flux (m s−1 ) • plotswitch, 0 = no plot, 1 = graph of fitted and observed (Tz − T0 )/(TL − T0 ) Output for the function is: qz in m s−1 .
14
Example script to call BPrun Warning: If you cut and paste the text below, you may have to re-type the ”’ symbols. Also, you’ll have replace the example ’datafolder’ to be the path to your data # -*- coding: utf-8 -*””” Created on Fri Dec 12 12:47:15 2014 A script to calculate qz from the Bredehoft and Papadopulos (1965) method using the heatfuncs library ””” from matplotlib.pyplot import * # plotting library, by the way # is the symbol for comments from numpy import * # scientific computing library import os # The opperating system library to change folders from heatfuncs import * # Finally, the heatfuncs library #The line below asigns the path to your temperature data to the variable ’datafolder’ #The r out the front is to say that this is in raw format. If the r isn’t used, you will #have to double all of the backslashes. datafolder = r’C:\your\path\goes\here’ # Changes the directory to where your data is located os.chdir(datafolder) # commands to load your data. Annoying point, Python counts from zero. In this example, # I have depth (m) in the 1st col of a file, and T (◦ C) in the 2nd col. This example has a # header row, which is skipped. For more complicated cases with gaps in the data, look up # the genfromtxt() command. To load csv files, change the delimiter=None to delimiter=’,’ z = loadtxt(’testdata.dat’,skiprows=1, delimiter=None, usecols=(0,)) T = loadtxt(’testdata.dat’,skiprows=1, delimiter=None, usecols=(1,)) # parameter values for functions z0 = 0.0 # Depth of top of geothermal profile, m zL = 100.0 # Depth of bot of geothermal profile, m pw = 1000.0 # water density kg m−3 cw = 4186.0 # specific heat capcity of water J kg−1◦ C−1 kb = 3.0 # bulk thermal conductivity W m−1◦ C−1 qest = 1.0e-7 # estimate of Darcy flux m s−1 plotswitch = 1 # 0 = no plot, 1 = show fitted data # Below is the BPrun function which will produce an estimate of q (m s−1 ) q = BPrun(T,z,z0,zL,qest,pw,cw,kb,plotswitch) print ’=================’ print ’ end of program’ print ’=================’ 15
References Bredehoeft, J.D., and Papadopulos, I.S. (1965) Rates of vertical groundwater movement estimated from the Earth’s thermal profile, Water Resources Research, 1(2), p 325-328. Lu, N. and Ge., S. (1996) Effect of horizontal heat and fluid flow on the vertical temperature distribution in a semiconfinig layer, Water Resources Research, 32(5), p 1449-1453. Mansure, A.J., and Reither, M. (1979) A vertical groundwater movement correction for heat flow, Journal of Geophysical Research, 84(B7), p 3490-3496. Taniguchi, M. (1993) Evaluation of vertical groundwater fluxes and thermal properties of aquifers based on transient temperature-depth profiles, Water Resources Research, 29(7), p 2012-2026.
16