# Import modules
import numpy as np
import math
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
51 Soil Temperature Model
Soil temperature is one of those variables that follow the theory closely. In this exrcise we will implement an analytical solution of the heat conduction-diffusion equation. The model is simple and accounts for soil temperature in time and soil depth. This model is robust enough to be used for research.
The model states that the annual mean temperature at all soil depths is the same. At increasing depths the thermal amplitude decreases and the timing of maximum and minimum temperatures experience a delay. This makes sense since it takes time for heat to move from the surface to deeper layers and the thermal fluctuations are higher near the surface than at depth as a consequence of heat losses primarily due to radiation and convection.
Model
T(z,t) = T_{avg} + A \ e^{-z/d} \ sin(\omega t - z/d - \phi)
T is the soil temperature at time t and depth z
z is the soil depth in meters
t is the time in days of year
T_{avg} is the annual average temperature at the soil surface
A is the thermal amplitude: (T_{max} + T_{min})/2.
\omega is the angular frequency: 2\pi / P
P is the period. Should be in the same units as t. The period is 365 days for annual oscillations and 24 hours for daily oscillations.
\phi is the phase constant, which is defined as: \frac{\pi}{2} + \omega t_0
t_0 is the time lag from an arbitrary starting point. In this case are days from January 1.
d is the damping depth, which is defined as: \sqrt{(2 D/ \omega)}. It has length units.
D is thermal diffusivity in m^2 d^{-1}. The thermal diffusivity is defined as \kappa / C
\kappa is the soil thermal conductivity in J m^{-1} K^{-1} d^{-1}
C is the soil volumetric heat capacity in J m^{-3} K^{-1}
Assumptions
Constant soil thermal diffusivity.
Uniform soil texture
Temperature in deep layers approximate the mean annual air temperature
In situation where we don’t have observations of soil temperature at the surface we also assume that the soil surface temperature is equal to the air temperature.
Model inputs
# Constants
= 25 # Annual average temperature at the soil surface
T_avg = 10 # Annual thermal amplitude at the soil surface
A0 = 0.203 # Thermal diffusivity obtained from KD2 Pro instrument [mm^2/s]
D = D / 100 * 86400 # convert to cm^2/day
D = 365 # days
period = 2*np.pi/period
omega = 15 # Time lag in days from January 1
t_0 = np.pi/2 + omega*t_0 # Phase constant
phi = (2*D/omega)**(1/2) # Damping depth
d D
175.39200000000002
Define model
# Define model as lambda function
= lambda doy,z: T_avg + A0 * np.exp(-z/d) * np.sin(omega*doy - z/d - phi) T_soilfn
Soil temperature for a specific depth as a function of time
= np.arange(1,366)
doy = 0
z = T_soilfn(doy,z)
T_soil
# Plot
plt.figure()
plt.plot(doy,T_soil) plt.show()
Soil temperature for a specific day of the year as a function of depth
= 10
doy = 100 # Number of interpolation
Nz = 500 # cm
zmax = np.linspace(0,zmax,Nz)
z = T_soilfn(doy,z)
T
plt.figure()-z)
plt.plot(T, plt.show()
Soil temperature as a function of both DOY and depth
= np.arange(1,366)
doy = np.linspace(0,500,1000)
z = np.meshgrid(doy,z)
doy_grid,z_grid
# Predict soil temperature for each grid
= T_soilfn(doy_grid,z_grid)
T_grid
# Create figure
= plt.figure(figsize=(10, 6), dpi=80) # 10 inch by 6 inch dpi = dots per inch
fig
# Get figure axes and convert it to a 3D projection
= fig.gca(projection='3d')
ax
# Add surface plot to axes. Save this surface plot in a variable
= ax.plot_surface(doy_grid, z_grid, T_grid, cmap='viridis', antialiased=False)
surf
# Add colorbar to figure based on ranges in the surf map.
=0.5, aspect=20)
fig.colorbar(surf, shrink
# Wire mesh
= surf = ax.plot_wireframe(doy_grid, z_grid, T_grid, linewidth=0.5, color='k', alpha=0.5)
frame
# Label x,y, and z axis
"Day of the year")
ax.set_xlabel('Soil depth [cm]')
ax.set_ylabel('Soil temperature \N{DEGREE SIGN}C')
ax.set_zlabel(
# Set position of the 3D plot
=30, azim=35) # elevation and azimuth. Change their value to see what happens.
ax.view_init(elev
plt.show()
Interactive plots
from bokeh.plotting import figure, show, output_notebook, ColumnDataSource, save
from bokeh.layouts import row
from bokeh.models import HoverTool
from bokeh.io import export_svgs
output_notebook()
# Set data for p1
= np.arange(1,366)
doy = 0
z = ColumnDataSource(data=dict(x=doy, y=T_soilfn(doy,z)))
source_p1
# Define tools for p1
= HoverTool(
hover_p1 =[
tooltips"Time (days)", "@x{0.}"),
("Temperature (Celsius)","@y{0.00}" )
(
]
)
# Create plots
= figure(y_range=[0,50],
p1 =400,
width=300,
height="Soil Temperature as a Function of Time",
title=[hover_p1],
tools="right")
toolbar_location
= 'Time [hours]'
p1.xaxis.axis_label = 'Temperature'
p1.yaxis.axis_label 'x','y',source=source_p1)
p1.line(
# Set data for p2
= 150
doy = np.linspace(0,500,100)
z = ColumnDataSource(data=dict(y=-1*z, x=T_soilfn(150,z)))
source_p2
# Define tools for p1
= HoverTool(
hover_p2 =[
tooltips"Depth (cm)","@y{0.0}"),
("Temperature (Celsius)","@x{0.00}")
(
]
)
# Create plots
= figure(y_range=[0,-500],
p2 =400,
width=300,
height="Soil Temperature as a Function of Soil Depth",
title=[hover_p2],
tools="right")
toolbar_location
= 'Temperature'
p2.xaxis.axis_label = 'Depth (cm)'
p2.yaxis.axis_label = 100
p2.min_border_left 'x','y',source=source_p2)
p2.line(
= "svg"
p1.output_backend = "svg"
p2.output_backend
show(row(p1,p2))
References
Wu, J. and Nofziger, D.L., 1999. Incorporating temperature effects on pesticide degradation into a management model. Journal of Environmental Quality, 28(1), pp.92-100.