Nothing
## TODO: document asymptotic behavior with distribute = TRUE, and k -> 100
## TODO: allow specification of M argument to bucket.sim
## TODO: allow specification of field capacity
#' @title Monthly Water Balances
#'
#' @description Perform a monthly water balance by "leaky bucket" model, inspired by code from `bucket.sim` of `hydromad` package, as defined in Bai et al., (2009) (model "SMA_S1"). The plant available water-holding storage (soil thickness * awc) is used as the "bucket capacity". All water in excess of this capacity is lumped into a single "surplus" term.
#'
#' @details See the [monthly water balance tutorial](http://ncss-tech.github.io/AQP/sharpshootR/monthly-WB.html) for further examples and discussion.
#'
#' A number of important assumptions are made by this style of water balance modeling:
#' * the concept of field capacity is built into the specified bucket size
#' * the influence of aquitards or local terrain cannot be integrated into this model
#' * interception is not used in this model
#'
#' @param AWC numeric, available water-holding capacity (mm), typically thickness (mm) * awc (fraction)
#'
#' @param PPT numeric, time-series of monthly PPT (mm), calendar year ordering
#'
#' @param PET numeric, time-series of monthly PET (mm), calendar year ordering
#'
#' @param S_init numeric, initial fraction of `AWC` filled with water (values 0-1)
#'
#' @param starting_month integer, starting month index, 1=January, 9=September
#'
#' @param rep integer, number of cycles to run water balance
#'
#' @param keep_last logical, keep only the last iteration of the water balance
#'
#' @param distribute logical, distribute monthly data into `k` divisions within each month
#'
#' @param method method for distributing PPT and PET into `k` divisions:
#' * 'equal' divides PPT and PET into `k` equal amounts
#' * 'random' divides PPT and PET into random proportions generated via multinominal simulation
#' * 'gaussian' divides PPT and PET according to a bell-shaped curve centered in the middle of each month
#'
#' @param k integer, number of divisions
#'
#'
#' @references
#'
#' Arkley R, Ulrich R. 1962. The use of calculated actual and potential evapotranspiration for estimating potential plant growth. Hilgardia 32(10):443-469.
#'
#' Bai, Y., T. Wagener, P. Reed (2009). A top-down framework for watershed model evaluation and selection under uncertainty. Environmental Modelling and Software 24(8), pp. 901-916.
#'
#' Farmer, D., M. Sivapalan, Farmer, D. (2003). Climate, soil and vegetation controls upon the variability of water balance in temperate and semiarid landscapes: downward approach to water balance analysis. Water Resources Research 39(2), p 1035.
#'
#' @return a `data.frame` with the following elements:
#'
#' * PPT: monthly PPT (mm)
#' * PET: monthly PET (mm)
#' * U: monthly surplus (mm)
#' * S: monthly soil moisture storage (mm)
#' * ET: monthly AET (mm)
#' * D: monthly deficit (mm)
#' * month: month number
#' * mo: month label
#'
monthlyWB <- function(AWC, PPT, PET, S_init = 1, starting_month = 1, rep = 1, keep_last = FALSE, distribute = FALSE, method = c('equal', 'random', 'gaussian'), k = 10) {
# sanity check
method <- match.arg(method)
# number of time steps in the original series
n <- length(PPT)
# re-order monthly data according to starting month
if(starting_month == 1) {
idx <- seq(from = starting_month, to = 12, by = 1)
} else {
idx <- c(seq(from = starting_month, to = 12, by = 1), seq(from = 1, to = (starting_month - 1), by = 1))
}
# replicate as needed
idx <- rep(idx, times = rep)
# re-index months as needed
PPT <- PPT[idx]
PET <- PET[idx]
# combine into format suitable for simulation
# .sim keeps track of cycle
d <- data.frame(P = PPT, E = PET, .sim = rep(1:rep, each = 12))
# add month index
d$month <- idx
.months <- c('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
d$mo <- .months[idx]
# convert to factor for proper sorting by split(), later when `distribute = TRUE`
d$mo <- factor(d$mo, levels = .months[idx][1:12])
# optionally spread-out PPT and PET over k bins within a month
if(distribute) {
# rows represent months
# there may be more than one simulation cycle
# i: current cycle/month
dd <- lapply(1:nrow(d), function(i, .k = k) {
# replicate row `i` within original data
.idx <- rep(i, times = .k)
.dr <- d[.idx, ]
# evenly distribute PPT and PET over k bins
if(method == 'equal') {
.dr$P <- .dr$P / .k
.dr$E <- .dr$E / .k
}
# multinominal simulation of proportions across k bins
# maybe more variability than expected...
if(method == 'random') {
# simulate k-proportions with equal probability
.p <- rep(1, times = .k)
# PPT
P.prop <- as.vector(rmultinom(n = 1, size = .k, prob = .p))
P.prop <- P.prop / sum(P.prop)
.dr$P <- P.prop * .dr$P[1]
# PET
E.prop <- as.vector(rmultinom(n = 1, size = .k, prob = .p))
E.prop <- E.prop / sum(E.prop)
.dr$E <- E.prop * .dr$E[1]
}
# distribute total PPT and PET following a Gaussian curve, centered at mean(1:k)
if(method == 'gaussian') {
# use Gaussian proportions, peaking at center of k-bins
# adjust steepness of peak with `sd` argument
k.s <- 1:.k
.p <- dnorm(k.s, mean = mean(k.s), sd = .k/5)
.p <- .p / sum(.p)
# distribute PPT
# all the same until overwritten
.dr$P <- .p * .dr$P[1]
# distribute PET
# all the same until overwritten
.dr$E <- .p * .dr$E[1]
}
## TODO: additional method using constant +/- fuzz
# keep track of simulation cycle
.dr$.sim <- .dr$.sim[1]
return(.dr)
}
)
d <- do.call('rbind', dd)
}
## hydromad interface
# note that first two columns are P, E
## TODO: check these assumptions:
# at the monthly time-step: a.ss = 0.01
# at the monthly time-step: M = 0
m <- hydromad::hydromad(d[, 1:2], sma = "bucket", routing = NULL)
m <- update(m, Sb = AWC, fc = 1, S_0 = S_init, a.ss = 0.01, M = 0, etmult = 1, a.ei = 0)
res <- predict(m, return_state = TRUE)
# ## custom implementation of "model S2" from Bai et al., 2009
# # soil moisture accounting "SMA_S2"
# # routing "R1"
# # SMA_S2 + R1
# #
# # important change:
# # ET[t] <- Eintc + min(S[t], (Etrans + Ebare))
# res <- .monthlyBucket(d, Sb = AWC, S_0 = S_init, fc = 1, a.ss = 0.01, M = 0, etmult = 1, a.ei = 0)
#
# combine original PPT, PET with results
res <- data.frame(d, res)
# cleanup names
names(res) <- c('PPT', 'PET', '.sim', 'month', 'mo', 'U', 'S', 'ET')
# compute deficit: AET - PET
res$D <- with(res, ET - PET)
# re-arrange
res <- res[, c('PPT', 'PET', 'U', 'S', 'ET', 'D', 'month', 'mo', '.sim')]
if(distribute) {
# flatten n simulations * k bins per month -> 12 months / simulation
# names and ordering as: sim.month
s <- split(res, list(res$.sim, res$mo), lex.order = TRUE)
s <- lapply(s, function(i, .k = k) {
# re-assemble into data.frame
.res <- data.frame(
# total PPT, PET, U
PPT = sum(i$PPT),
PET = sum(i$PET),
U = sum(i$U),
# final storage value
S = i$S[.k],
# total AET
ET = sum(i$ET),
# compute D last
D = 0,
# these are constant, keep first
month = i$month[1],
mo = i$mo[1],
.sim = i$.sim[1]
)
return(.res)
})
res <- do.call('rbind', s)
# compute Deficit after summing PPT and ET separately
# deficit is a negative value
res$D <- res$ET - res$PET
}
# check overall water balance for missing mass
# rounding to 0.01 mm should be sufficient precision
#
# P = U + ET + dS
# for all time steps: .in = .out + dS
# final mass balance: sum(.in - (.out + .dS))
# PPT
.in <- res$PPT
# AET + U
.out <- res$ET + res$U
# dS
# more complicated than seems, when S_init < 1
# S at time 0 = AWC * S_init
.dS <- diff(c(AWC * S_init, res$S))
# mass balance
.mb <- sum(.in - (.out + .dS))
# save for later use
attr(res, 'mass.balance') <- round(.mb, 0.01)
## TODO: think about what an error in the mass balance means...
if(abs(.mb) > 5) {
message('Mass balance not closed')
}
# optionally keep the last simulation cycle
.last <- max(res$.sim)
if(keep_last) {
res <- res[which(res$.sim == .last), ]
}
# reset row names
row.names(res) <- NULL
# add original AWC used as an attribute
attr(res, 'AWC') <- AWC
# done
return(res)
}
##
## temporary R-based implementation here
##
# https://github.com/hydromad/hydromad/blob/master/R/bucket.R
# https://github.com/hydromad/hydromad/blob/master/src/bucket.c
# https://github.com/hydromad/hydromad/issues/190
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.