# Import modules
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from pprint import pprint
70 Soil moisture drydowns
Soil moisture drydowns refer to the rate at which soil loses its moisture content over time, typically following a rainfall event. The initial rate of moisture loss is typically rapid, slowing down as the soil reaches a lower moisture content. Thus, this process is often described by an exponential decay model.
In this exercise we will extract drydown events from a time series of rootzone soil moisture. Basically, a drydown represents the period of moisture loss between precipitation events. Since in this region small rainfall events don’t usually contribute to appreciable soil moisture recharge, we will set a tolerance level to ignore small rainfall events.
Model description
SWC = A \ e^{-t/\tau} + \theta_{res}
SWC = Soil water content in m^{3}/m^{3}
A = The initial soil water content m^{3}/m^{3}. Soil water at time t=0
t = Days since rainfall event
\tau = Constant that modulates the rate at which the soil dries
\theta_{res} = Residual soil water content m^{3}/m^{3}.
# Define model using an anonymous lamda function
= lambda t,tau,A,S_min: A * np.exp(-t/tau) + S_min
model xrange = np.arange(30)
# Create figure with example drydowns
=(5,4))
plt.figure(figsize
# Rapid decay. Typical of summer, coarse soils, and actively growing vegetation
xrange, model(xrange,5,70,50), color='green')
plt.plot(
# Drydowns during moderate atmospheric demand (spring and fall)
xrange, model(xrange,20,70,50), color='tomato')
plt.plot(
# Drydown during low atmospheric demand (winter)
xrange, model(xrange,100,70,50), color='navy')
plt.plot(
'Days since last rainfall')
plt.xlabel('Storage (mm)')
plt.ylabel( plt.show()
Load dataset
# Load data
= pd.read_csv('../datasets/kings_creek_2022_2023_daily.csv',parse_dates=['datetime'])
df df.head()
datetime | pressure | tmin | tmax | tavg | rmin | rmax | prcp | srad | wspd | wdir | vpd | vwc_5cm | vwc_20cm | vwc_40cm | soiltemp_5cm | soiltemp_20cm | soiltemp_40cm | battv | discharge | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2022-01-01 | 96.838 | -14.8 | -4.4 | -9.60 | 78.475 | 98.012 | 0.25 | 2.098 | 5.483 | 0.969 | 0.028 | 0.257 | 0.307 | 0.359 | 2.996 | 5.392 | 7.425 | 8714.833 | 0.0 |
1 | 2022-01-02 | 97.995 | -20.4 | -7.2 | -13.80 | 50.543 | 84.936 | 0.25 | 9.756 | 2.216 | 2.023 | 0.072 | 0.256 | 0.307 | 0.358 | 2.562 | 4.250 | 6.692 | 8890.042 | 0.0 |
2 | 2022-01-03 | 97.844 | -9.4 | 8.8 | -0.30 | 40.622 | 82.662 | 0.50 | 9.681 | 2.749 | 5.667 | 0.262 | 0.255 | 0.307 | 0.358 | 2.454 | 3.917 | 6.208 | 8924.833 | 0.0 |
3 | 2022-01-04 | 96.419 | 0.1 | 8.6 | 4.35 | 48.326 | 69.402 | 0.25 | 8.379 | 5.806 | 2.627 | 0.363 | 0.289 | 0.319 | 0.357 | 2.496 | 3.754 | 5.842 | 8838.292 | 0.0 |
4 | 2022-01-05 | 97.462 | -11.1 | -2.2 | -6.65 | 50.341 | 76.828 | 0.00 | 5.717 | 4.207 | 1.251 | 0.126 | 0.313 | 0.337 | 0.357 | 1.688 | 3.429 | 5.567 | 8848.083 | 0.0 |
# Convert date strings into pandas datetie format
1, 'doy', df['datetime'].dt.dayofyear) df.insert(
# Compute soil water storage in top 50 cm
'storage'] = df['vwc_5cm']*100 + df['vwc_20cm']*200 + df['vwc_40cm']*200 df[
# Plot timeseries of soil moisture and EDDI
=(8,3))
plt.figure(figsize
'datetime'], df['storage'], color='k', linewidth=1.0)
plt.plot(df['Soil water storage (mm)')
plt.ylabel(
plt.twinx()
'datetime'], df['prcp'], color='tomato', linewidth=0.5)
plt.plot(df['Precipitation (mm)', color='tomato')
plt.ylabel(
plt.show()
# Find residual volumetric water content
= df['storage'].min()
storage_min print(storage_min)
# Define model by forcing minimum storage
= lambda t,tau,A: A * np.exp(-t/tau) + storage_min model
90.80000000000001
# Iterate over soil moisture timeseries to retrieve drydowns
= 0
day_counter = 7
drydown_min_length = []
all_drydowns = {'date':[],'storage':[],'doy':[],
drydown_event 'days':[],'length':[], 'par':[]}
# We start the loop on the second day
for i in range(1,len(df)):
= df["storage"][i] - df["storage"][i-1]
delta
if delta < 0:
+= 1
day_counter 'date'].append(df.loc[i,'datetime'])
drydown_event['storage'].append(df.loc[i,'storage'])
drydown_event['doy'].append(df.loc[i,'doy'])
drydown_event['days'].append(day_counter)
drydown_event['length'] = day_counter
drydown_event[
else:
# Avoid saving data for short drydowns
if day_counter < drydown_min_length:
# Reset variables
= 0
day_counter = {'date':[],'storage':[],'doy':[],
drydown_event 'days':[],'length':[], 'par':[]}
continue
else:
# Fit model to drydown event
= curve_fit(model,
par_opt, par_cov 'days'],
drydown_event['storage'])
drydown_event['par'] = par_opt
drydown_event[
# Append current event
all_drydowns.append(drydown_event)
# Reset variables
= 0
day_counter = {'date':[],'storage':[],'doy':[],
drydown_event 'days':[],'length':[], 'par':[]}
print('There are a total of',len(all_drydowns),'drydowns')
There are a total of 34 drydowns
# Inspect one drydown event
2]) pprint(all_drydowns[
{'date': [Timestamp('2022-04-12 00:00:00'),
Timestamp('2022-04-13 00:00:00'),
Timestamp('2022-04-14 00:00:00'),
Timestamp('2022-04-15 00:00:00'),
Timestamp('2022-04-16 00:00:00'),
Timestamp('2022-04-17 00:00:00'),
Timestamp('2022-04-18 00:00:00'),
Timestamp('2022-04-19 00:00:00'),
Timestamp('2022-04-20 00:00:00')],
'days': [1, 2, 3, 4, 5, 6, 7, 8, 9],
'doy': [102, 103, 104, 105, 106, 107, 108, 109, 110],
'length': 9,
'par': array([156.17218773, 76.60354863]),
'storage': [167.0,
166.4,
165.8,
165.60000000000002,
164.9,
164.5,
164.1,
163.6,
163.1]}
Overlay soil moisture timeseries and extracted drydowns
=(8,3))
plt.figure(figsize'datetime'], df['storage'], color='k', linewidth=1.0)
plt.plot(df[for event in all_drydowns:
'date'], model(np.asarray(event['days']), *event['par']), '-r')
plt.plot(event[
'Soil water storage (mm)')
plt.ylabel( plt.show()