diff --git a/ch11.qmd b/ch11.qmd index c678c64..41bd3a0 100644 --- a/ch11.qmd +++ b/ch11.qmd @@ -1,375 +1,11 @@ --- title-block-banner: true bibliography: references.bib -editor: - markdown: - wrap: sentence --- -## Field Soil Water Balance +# Field Soil Water Balance -We will develop a 2-layer simple soil water balance modeling for simulating 1-D fluxes in the rootzone using USCRN station at Stillwater, OK, USA as an example. -Let us extract sample data from the sample station for 2021. +```{r child="ch11_a.qmd", message = FALSE, warning = FALSE} -```{r 11a1, message = FALSE, warning = FALSE} -# Yearly data from the sample station -CRNdat = read.csv(url("https://www.ncei.noaa.gov/pub/data/uscrn/products/daily01/2021/CRND0103-2021-OK_Stillwater_2_W.txt"), header=FALSE,sep="") -# Data headers -headers=read.csv(url("https://www.ncei.noaa.gov/pub/data/uscrn/products/daily01/headers.txt"), header=FALSE,sep="") - -# Column names as headers from the text file -colnames(CRNdat)=headers[2,1:ncol(CRNdat)] - -# Replace fill values with NA -CRNdat[CRNdat == -9999]=NA -CRNdat[CRNdat == -99]=NA -CRNdat[CRNdat == 999]=NA - -# View data sample -library(kableExtra) -dataTable = kbl(head(CRNdat,6),full_width = F) -kable_styling(dataTable,bootstrap_options = c("striped", "hover", "condensed", "responsive")) -``` - -### Exploratory Data Analysis for the Sample Station - -```{r 11a2, message = FALSE, warning = FALSE} - # Create Date array for the station - library(zoo) - library(cowplot) - library(ggplot2) - - # Time (in days) - datetime=as.Date(as.character(CRNdat$LST_DATE), format= "%Y%m%d") - - # Surface soil moisture (in m3/m3) - SSM=na.approx(CRNdat$SOIL_MOISTURE_5_DAILY,na.rm = FALSE) - # Soil moisture at 50 cm depth (in m3/m3) - SM50=na.approx(CRNdat$SOIL_MOISTURE_50_DAILY,na.rm = FALSE) - - # Precipitation (in mm) - PCP=CRNdat$P_DAILY_CALC - PCP[PCP<0]=0 # Negative (flagged values are replaced by 0) - PCP[is.na(PCP)]=0 # NA values are replaced by 0) - - # Create a data frame of SM-PCP dataset for plotting - df=data.frame(Date=datetime, PCP=PCP, SSM=SSM, SM50=SM50) - - # Plot for surface soil moisture - p1 = ggplot(df) + - geom_line(aes(Date, SSM, color = "SSM")) + - geom_line(aes(Date, SM50, color = "SM50")) + - scale_y_continuous(position = "left", - limits = c(0.05,0.475), - expand = c(0,0)) + - scale_color_manual(values = c("steelblue", "cyan4")) + - guides(x = guide_axis(angle = 90)) + - scale_x_date(date_breaks = "month", - limits = c(datetime[1], - datetime[365]), - expand = c(0,0)) + - labs(y = "Soil moisture [v/v]", - x = "Date") + - theme_minimal() + - theme(axis.title.y.left = element_text(hjust = 0), - legend.position = "bottom", - legend.justification = c(0.1, 0.5), - legend.title = element_blank()) - - # Plot for precipitation - p2= ggplot(df) + - geom_col(aes(Date, PCP, fill = "Total Daily Precipitation")) + - scale_y_reverse(position = "right", - limits = c(100,0), - breaks = c(0,50,100), - labels = c(0,50,100), - expand = c(0,0)) + - scale_x_date(date_breaks = "month", - limits = c(datetime[1], datetime[365]), - expand = c(0,0)) + - scale_fill_manual(values = c("sienna1")) + - guides(x = guide_axis(angle = 90)) + - labs(y = "Precipitation [mm]", x = "") + - theme_minimal() + - theme(axis.title.y.right = element_text(hjust = 0), - legend.position = "bottom", - legend.justification = c(0.75, 0.5), - legend.title = element_blank()) - -aligned_plots = cowplot::align_plots(p1, p2, align = "hv", axis = "tblr") -p3=ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]]) -print(p3) -``` - -## Low-pass Filtering of Meteorological Perturbations Through Soil Profile - -```{r, message = FALSE, warning = FALSE} -#| label: fig-density -#| fig-cap: "Soil temperature and moisture response with profile depth" -#| fig-subcap: -#| - "Depth versus soil temperature" -#| - "Depth versus soil moisture" -#| layout-ncol: 2 - -# Profile depth versus soil temperature -Tempdf=data.frame(T5=na.approx(CRNdat$SOIL_TEMP_5_DAILY,na.rm = FALSE), - T10=na.approx(CRNdat$SOIL_TEMP_10_DAILY,na.rm = FALSE), - T20=na.approx(CRNdat$SOIL_TEMP_20_DAILY,na.rm = FALSE), - T50=na.approx(CRNdat$SOIL_TEMP_50_DAILY,na.rm = FALSE), - T100=na.approx(CRNdat$SOIL_TEMP_100_DAILY,na.rm = FALSE)) - -df=rbind(data.frame(Temp=Tempdf$T5, Depth=5,Date= datetime), -data.frame(Temp=Tempdf$T10, Depth=10, Date=datetime), -data.frame(Temp=Tempdf$T20, Depth=20,Date= datetime), -data.frame(Temp=Tempdf$T50, Depth=50,Date=datetime), -data.frame(Temp=Tempdf$T100, Depth=100, Date=datetime)) - -ggplot(df, aes(x = Date, y = Depth)) + - geom_contour_filled(aes(z = Temp), bins = 6, stat = "contour_filled") + - scale_fill_brewer(palette = "YlOrRd",direction = 1) + - labs(x = "Date", y = "Depth (cm)", fill = "Temperature (°C)") + - theme_minimal() + - scale_y_reverse() - -# Profile depth versus soil moisture -SMpdf=data.frame(SM5=na.approx(CRNdat$SOIL_MOISTURE_5_DAILY,na.rm = FALSE), - SM10=na.approx(CRNdat$SOIL_MOISTURE_10_DAILY,na.rm = FALSE), - SM20=na.approx(CRNdat$SOIL_MOISTURE_20_DAILY,na.rm = FALSE), - SM50=na.approx(CRNdat$SOIL_MOISTURE_50_DAILY,na.rm = FALSE), - SM100=na.approx(CRNdat$SOIL_MOISTURE_100_DAILY,na.rm = FALSE)) - -df=rbind(data.frame(SM=SMpdf$SM5, Depth=5,Date= datetime), -data.frame(SM=SMpdf$SM10, Depth=10, Date=datetime), -data.frame(SM=SMpdf$SM20, Depth=20,Date= datetime), -data.frame(SM=SMpdf$SM50, Depth=50,Date=datetime), -data.frame(SM=SMpdf$SM100, Depth=100, Date=datetime)) - -ggplot(df, aes(x = Date, y = Depth)) + - geom_contour_filled(aes(z = SM), bins = 6, stat = "contour_filled") + - scale_fill_brewer(palette = "YlGnBu") + - labs(x = "Date", y = "Depth (cm)", fill = "Soil Moisture") + - theme_minimal() + - scale_y_reverse() - -``` - -Under hydrologic equilibrium, the analytical solution of the 1-dimensional vertical water balance (differential) equation can be approximated using the relationship between the temporal changes in the rootzone soil moisture (RZSM) and the difference in SSM vis-à-vis the antecedent rootzone conditions (Wagner et al., 1999, Albergel et al., 2008, Manfreda et al., 2014). -A simple recursive exponential LP filter to simulate the 1-dimensional first-order infinite-impulse response of RZSM to temporal variability in SSM is given as follows: - -$$ SM_t^{'}= SM_{t-1}^{'}+ D_p(SSM_t-SM_{t-1}^{'}) $$ {#eq-11.1} - -where, $t \ge1$; when $t=1$, $SM_{t=0}^{'}=SSM_{t=0}$. - -$SM_{t}^{'}$ is the temporally smoothed (filtered) SSM at time $t$ . - -Parameter $D_p$ is the exponential filter smoothing factor of the soil profile $[-]$, also called the *pseudo-diffusivity coefficient*; $D_p$ ∈ \[0,1\]. -It represents the effective influence of various bio-geo-physical controls such as vegetation, topography, hydroclimatology and pedological characteristics (soil profile thickness, effective soil hydraulic characteristics, etc.) on vertical fluxes between the soil surface and the rootzone. - -Eq. -1 provides a first-order approximation of the infinite-impulse response of RZSM to temporal variability in SSM as $SM^{'}$, and yields the exponential weighted average of the antecedent observations using weights proportional to the terms of the geometric progression: $1, (1-D_p), (1-D_p)^2, ….(1-D_p)^n$, $-$ the discrete form of an exponential function (Hunter, 1986; Perry, 2010). -This method makes two key assumption: - -1. The lateral moisture flux to/from the rootzone is negligible, and, - -2. The groundwater table is significantly deeper than the rootzone depth. - -### Origins of Low-pass Filter Relationship in Vertical Soil Fluxes - -The origins of the low-pass filter effect of soil profile on surface variability in soil moisture can be traced to the original equations for vertical flux of water int he unsaturated soil profile. -We recall the Diffusivity version of Richard's equation as follows: - -$$ -\frac{\partial\theta} {\partial t}= \frac{\partial}{\partial z}(D(\theta)\frac{\partial\theta}{\partial z})-\frac{\partial K(\theta)}{\partial z} -$$ {#eq-11.2} - -where, $D$ and $K$ are the hydraulic diffusivity and conductivity of the soil profile, and are a function of the water content of the soil profile, $z$ is the height above the bottom of the soil profile and $\theta$ is the soil moisture content. -The right hand side of this equation contains two terms, where $\partial/\partial z (D(\theta)\partial/\partial z)$ accounts for the flux due to the suction gradient in the profile, while $\partial K(\theta)/\partial z$ pertains to the role of gravity in the vertical flow of water. -Given the equivalent suction exerted by the atmosphere is several order of magnitude compare to the gravitational head gradient, we can simplify the preceeding equation as: - -$$ -\frac{\partial\theta} {\partial t}= \frac{\partial}{\partial z}(D(\theta)\frac{\partial\theta}{\partial z}) -$$ {#eq-11.3} - -First integral of this equation can be written as: $$ -\frac{d\theta}{dt}=D(\theta)\frac{d\theta}{dz}+C -$$ {#eq-11.4} where, $C$ is the constant of integration. -Since there is no flow at the bottom boundary of the soil profile, $d\theta/dz=0$ . -Hence, $C=0$. -Writing the preceding equation in discrete form, we get: - -$$ -\frac{\Delta\theta}{\Delta t}=D(\theta)\frac{\Delta\theta}{\Delta z} -$$ {#eq-11.5} - -As the soil reaches the second phase of drying, it enters a phase where the rate of soil drydown is slow, and the rate of loss of water from all depths can be assumed to be the same. -Simplifying the previous equation for a simple 2-layer representaiton of soil profile, with $SSM$ and $SM$ representing the surface and rootzone soil moisture respectively, we can write Eq. -5 as:\ -$$ -SM_t-SM_{t-1}=D(SM) \times (SSM_{t}-SM_{t-1}) -$$ {#eq-11.6} - -or, - -$$ -SM_t=SM_{t-1}+ D(SM) \times (SSM_{t}-SM_{t-1}) -$$ {#eq-11.7} - -::: {.callout-tip icon="TRUE"} -## Note - -[This equation is analogous to the low-pass filter equation provided earlier. Given the true form of the relationship between the hydraulic diffusivity (or pseudo-diffusivity) with the soil moisture content is not known in the field, often times, the value of $D$ and $D_p$ is assumed to be constant for simplicity.]{style="color:blue;"} -::: - -### Propagation of Drying Front in the Soil Profile - -We will demonstrate the application of low-pass filter to capture the propagation of drying front from the surface to the deeper profiles. - -```{r, message = FALSE, warning = FALSE} -# Function for implementing LP filter on surface soil moisture -lp_filt=function(SSM, Dp){ - # PARAMETERS--- - # SSM= Surface soil moisture - # Dp= Pseudo-diffusivity coefficient - # RETURNS--- - # SSMf=filtered surface soil moisture (excluding the spinup period) - - SSMf=c() # Empty array to store filtered SSM - buffer=0.01*(max(SSM)-min(SSM)) # To decrease sensitivity to noise in sensor readings (1% of SSM range) - - # Apply recursive filter - SSMf[1]=SSM[1] # Initialize the series - for (t in 2:length(SSM)) - SSMf[t]=ifelse(SSM[t] > (SSM[t-1]+buffer),max(SSM[t], SSMf[t-1]),SSMf[t-1]+Dp*(SSM[t]-SSMf[t-1])) - return(SSMf) -} - -# Filtered surface soil moisture with varying values of Dp -SSM=SMpdf$SM5 # Surface soil moisture -SSMf_1=lp_filt(SSM, Dp= 0.1) -SSMf_05=lp_filt(SSM, Dp= 0.05) -SSMf_02=lp_filt(SSM, Dp= 0.02) - -# Plot surface soil moisture with filtered time series -plot(SSM, type="l", col="gray80", lwd=3, ylab="Value", xlab="Time", -main="Filtered v/s observed SSM for varying Dp") -lines(SSMf_1, col="red") -lines(SSMf_05, col="purple") -lines(SSMf_02, col="blue") - -# Normalized filtered surface soil moisture -SSMf_1norm=(SSMf_1-min(SSMf_1))/(max(SSMf_1)-min(SSMf_1)) -SSMf_05norm=(SSMf_05-min(SSMf_05))/(max(SSMf_05)-min(SSMf_05)) -SSMf_02norm=(SSMf_02-min(SSMf_02))/(max(SSMf_02)-min(SSMf_02)) - -# Weighted average of soil moisture at multiple depths to estimate RZSM -RZSM=(SMpdf$SM5*(7.5-0)+ # 0- 7.5 cm depth -SMpdf$SM10*(15-7.5)+ # 7.5-15 cm depth -SMpdf$SM20*(35-15)+ # 15-35 cm depth -SMpdf$SM50*(75-35)+ # 35-75 cm depth -SMpdf$SM100*(100-75))/100 # 75-100 cm depth - -rzsm_norm=(RZSM-min(RZSM))/(max(RZSM)-min(RZSM)) -ssm_norm=(SSM-min(SSM))/(max(SSM)-min(SSM)) - -plot(rzsm_norm, type="l", col="gray80", lwd=3, ylab="Value", xlab="Time", -main="Comparison of RZSM with SSM and filtered SSM (normalized to 0-1)") -lines(ssm_norm, col="red") -lines(SSMf_05norm, col="blue") -``` - -## Priestley-Taylor Method for Estimating Potential ET (PET) - -This formulation is based on physical principles and can be seen as a simplified version of the Penman-Monteith formula for calculating evapotranspiration. -The simplification involves removing the aerodynamic terms from the Penman-Monteith equation and using a constant ($α$), which has been empirically derived and is estimated to be 1.26 for open bodies of water, but can range from \<1 (humid conditions) to almost 2 (arid conditions). -This constant tends to be higher in arid regions or areas experiencing significant water stress. -The Priestley-Taylor formula can be used for calculating daily evapotranspiration and can also be applied to smaller time intervals (hourly, for example) if the necessary data is available. - -$$ -\text { PET }=\left(\frac{\alpha}{\lambda_v \rho_{\mathrm{w}}}\right) \times \frac{\Delta}{\Delta+\gamma} \times \text {R} -$$ - -if $T_{mean}< 0^oC$ - -$$ -\Delta=0.3405 \times \mathrm{e}^\left(0.0642 \mathrm{~T}_{\text {mean}}\right) \\ -$$ - -if $T_{mean}> 0^oC$ $$ -\Delta=0.3221 \times \mathrm{e}^\left(0.0803 \mathrm{~T}_{\mathrm{mean}}^{0.8876}\right) -$$ - -where, - -> $PET$= Daily potential evapotranspiration ($m$) -> -> $λv$ = Latent heat of vaporization (2260 $kJ/kg$) -> -> $ρw$ = Density of water (1000 $kg/m^3$) -> -> $γ$ = Psychrometric constant ( 4.95x $10^{-4}$ $kg/m^3/^oC$) -> -> $Rad$ = Daily solar radiation incident on a horizontal earth surface ($kg/m^2$) -> -> $Δ$ = Slope of the saturation vapor density curve from the psychrometric chart ($kg/^oC$) -> -> $T_{mean}$ = Daily mean air temperature ($^oC$) -> -> $\alpha$ = Priestley-Taylor correction factor - -```{r 11a4, message = FALSE, warning = FALSE} -# Parameters for Priestley-Taylor formula -lambdaV= 2260 # Latent heat of vaporization -rhoW= 1000 # Density of water -gamma= 4.95*1e-4 # Psychrometric constant -alpha=1.2 # Priestley-Taylor correction factor - -# PET estimation using Priestley-Taylor method - PT_PET=function(Tmean,Rad,alpha){ - - deltaVal=rep(0, length(Tmean)) # Create empty array - - # When Tmean<0 - deltaVal[Tmean<0]=0.3405*exp(0.0642*Tmean[Tmean<0]) - # When Tmean>=0 - deltaVal[Tmean>=0]=0.3221*exp(0.0803*((Tmean[Tmean>=0])^0.8876)) - - # Priestley-Taylor equation - PETval=(alpha/(lambdaV*rhoW))*(deltaVal/(deltaVal+gamma))*Rad - PETval[PETval<0]=0 - return(PETval) #estimate in m - } -``` - -As we can see from the function above, Priestley-Taylor formula requires mean temperature and solar radiation as inputs. -So, we will collect the meteorological dataset namely *a*) Mean daily temperature *b*) Total solar radiation from the sample station and estimate the value of total daily potential evapotranspiration (in m). -We will also store total daily precipitation, which will be used as model forcing. - -```{r 11a5, message = FALSE, warning = FALSE} - # Observed meteorological forcings - Rad=CRNdat$SOLARAD_DAILY*1000 # (as 1 MJ= 1000 KJ) - Tmean=CRNdat$T_DAILY_MEAN - PCP=CRNdat$P_DAILY_CALC/10 # convert units from mm to cm - - # Linear gap-filling of missing samples and removing flagged samples - library(zoo) - Tmean=na.approx(Tmean,na.rm = FALSE) - Rad=na.approx(Rad,na.rm = FALSE) - - PCP[PCP<0]=0 # Negative (flagged values are replaced by 0) - PCP[is.na(PCP)]=0 # NA values are replaced by 0) - - # Create data frame for met forcings for the station - datetime=as.Date(as.character(CRNdat$LST_DATE), format= "%Y%m%d") - forcings=data.frame(Date=datetime,PCP, Rad,Tmean) - - # Apply Priestley-Taylor method for PET estimation using the PT_PET function - PET=PT_PET(Tmean,Rad,alpha) - - # Plot estimated PET time series - ggplot(data=forcings)+ - geom_line(aes(x = datetime, y = PET), color="steelblue") + - xlab("Date") + # Add labels to the axes - ylab("PET [in m]")+ - theme_bw() ```