# 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, drydown, exponential decay
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()