8.5 Numerical Implementation
8.5.2 Soil Functions
Numerical Implementation 185 inf.oldTheta[i] = inf.theta[i]
sumInfiltration += flux * dt time += dt
print("time =", int(time), "\tdt =", int(dt),
"\tIter. =", int(nrIterations),
"\tInf:", format(sumInfiltration, ’.3f’)) myPlot[0].clear()
myPlot[0].set_xlim(0, 0.5)
myPlot[0].set_xlabel("Water content [m$^3$ m$^{-3}$]") myPlot[0].set_ylabel("Depth [m]")
myPlot[0].plot(inf.theta, -inf.z, ’r-’) myPlot[0].plot(inf.theta, -inf.z, ’yo’) myPlot[1].plot(time, flux, ’ro’)
plt.draw()
if (float(nrIterations/inf.maxNrIterations) < 0.25):
dt = min(dt*2, maxTimeStep) else:
dt = max(dt / 2, 1) for i in range(inf.n+1):
inf.theta[i] = inf.oldTheta[i]
if (solver == inf.NEWTON_RAPHSON_MFP):
inf.psi[i] = inf.MFPFromTheta(soil, inf.theta[i]) else:
inf.psi[i] = inf.waterPotential(funcType, soil, inf.theta[i]) print ("dt =", dt, "No convergence")
print("nr of iterations per hour:", totalIterationNr / simulationLenght)
plt.ioff() plt.show() main()
The algorithm has an adaptive time step. If the ratio between the number of iterations and the maximum number of iterations is less than 0.25, then the selected time step is the minimum value between 2tand the maximum time step. Otherwise, the time step is the maximum value betweendt/2 and 1. The algorithm also has the option of using a linear or a geometric grid, which is generated by the same functions used for solution of gas flow in Chapter 3. The filePSP_grid.pycontains the algorithm.
186 Transient Water Flow
curve, hydraulic conductivity, capacity and means of hydraulic conductivities. In the following chapters, the filePSP_soil.pywill also contain additional soil properties.
The first lines are written to define variables that will be used to select between differ- ent hydraulic functions. The following lines are written to define variables used to select the numerical scheme and then those to choose the type of mean used for computation of hydraulic conductivity across different elements. The classCsoilis defined. Note here the use of object-oriented programming (OOP) through the use of a class. The reader should read the section on OOP in Appendix A for details about OOP in Python.
The functionreadSoil( ) reads the data contained in the filesoil.text, by employing the functionreadDataFile, which has been used and described in previous chapters.
The functionreadSoil( ) reads all the rows of the filesoil.txtand to each row a ho- rizon is associated that is an instance of the classCsoil. The user can therefore include as many horizons as needed. The function returnssoil[ ], which is a list of instances.
In the example presented here, the list has length = 3. The functiongetHorizonIndex returns the horizon index (given the depths). The horizon index begins at zero; there- fore, in this case, they are 0,1 and 2. There is a control to be sure that there are no holes or missing depth intervals. The total depth of the soil profile is the lower depth of the last horizon.
The selection of the air-entry potential is performed in the function airEntryPo- tential( ). When the fitting of water retention data is done with the Campbell and the modified van Genuchten equations the air-entry values may be different and so two sets of parameters can be read from the filesoil.txt; therefore different values are used in the numerical solution. The following functions are written to obtain relationships between variables, for instance to convert from degree of saturation to volumetric water content, or to define the water retention curve for the different models and obtain the degree of saturation.
#PSP_soil.py
from PSP_readDataFile import * from math import sqrt, log g = 9.8065
NODATA = -9999.
CAMPBELL = 1 RESTRICTED_VG = 2 IPPISCH_VG = 3 VAN_GENUCHTEN = 4 CELL_CENT_FIN_VOL = 1 NEWTON_RAPHSON_MP = 2 NEWTON_RAPHSON_MFP = 3 LOGARITHMIC = 0
HARMONIC = 1 GEOMETRIC = 2
Numerical Implementation 187 class Csoil:
upperDepth = NODATA lowerDepth = NODATA Campbell_he = NODATA Campbell_b = NODATA CampbellMFP_he = NODATA Campbell_b3 = NODATA VG_alpha = NODATA VG_n = NODATA VG_m = NODATA VG_he = NODATA
VG_alpha_mod = NODATA VG_n_mod = NODATA VG_m_mod = NODATA VG_Sc = NODATA VG_thetaR = NODATA Mualem_L = NODATA thetaS = NODATA Ks = NODATA
def readSoil(soilFileName):
mySoil = []
A, isFileOk = readDataFile(soilFileName, 1, ’,’, False) if ((not isFileOk) or (len(A[0]) < 12)):
print("error: wrong soil file.") return False, mySoil
for i in range(len(A)):
horizon = Csoil()
horizon.upperDepth = A[i,0]
horizon.lowerDepth = A[i,1]
horizon.Campbell_he = A[i,2]
horizon.Campbell_b = A[i,3]
horizon.Campbell_n = 2.0 + (3.0 / horizon.Campbell_b) horizon.VG_he = A[i,4]
horizon.VG_alpha = A[i,5]
horizon.VG_n = A[i,6]
horizon.VG_m = 1. - (1. / horizon.VG_n) horizon.VG_alpha_mod = A[i,7]
horizon.VG_n_mod = A[i,8]
horizon.VG_m_mod = 1. - (1. / horizon.VG_n_mod) horizon.VG_Sc =((1.+ (horizon.VG_alpha_mod*
abs(horizon.VG_he))**horizon.VG_n_mod)**(-horizon.VG_m_mod)) horizon.VG_thetaR = A[i,9]
horizon.thetaS = A[i,10]
horizon.Ks = A[i,11]
horizon.Mualem_L = 0.5
horizon.CampbellMFP_he = (horizon.Ks * horizon.Campbell_he
188 Transient Water Flow
/ (1.0 - horizon.Campbell_n))
horizon.Campbell_b3 = ((2.0 * horizon.Campbell_b + 3.0) / (horizon.Campbell_b + 3.0))
mySoil.append(horizon) return True, mySoil
def getHorizonIndex(soil, depth):
for index in range(len(soil)):
if ((depth >= soil[index].upperDepth) and (depth < soil[index].lowerDepth)):
return(index) lastHorizon = len(soil)-1
if depth >= soil[lastHorizon].lowerDepth:
return lastHorizon else:
return(-1)
def airEntryPotential(funcType, soil):
if (funcType == CAMPBELL):
return(soil.Campbell_he) elif (funcType == IPPISCH_VG):
return(soil.VG_he)
elif (funcType == RESTRICTED_VG):
return(0) else:
return(NODATA)
def waterPotential(funcType, soil, theta):
psi = NODATA
Se = SeFromTheta(funcType, soil, theta) if (funcType == RESTRICTED_VG):
psi = (-(1./soil.VG_alpha)*
((1./Se)**(1./soil.VG_m) - 1.)**(1./soil.VG_n)) elif (funcType == IPPISCH_VG):
psi = -((1./soil.VG_alpha_mod)*
((1./(Se*soil.VG_Sc))**(1./soil.VG_m_mod)-1.)**(1./soil.VG_n_mod)) elif (funcType == CAMPBELL):
psi = soil.Campbell_he * Se**(-soil.Campbell_b) return(psi)
def SeFromTheta(funcType, soil, theta):
if (theta >= soil.thetaS): return(1.) if (funcType == CAMPBELL):
Se = theta / soil.thetaS else:
Se = (theta - soil.VG_thetaR) / (soil.thetaS - soil.VG_thetaR) return (Se)