--- title: "k-HLR relationships" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{k-HLR relationships} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", echo = TRUE ) ``` ```{r setup, include=FALSE,warning = FALSE} library(knitr) library(kableExtra) ``` The purpose of this vignette is to explain a way to derive "equivalent"k-HLR relationships that let you translate between Dynamic DMSTA-style P model parameters (ie. $K_1$, $K_2$, $K_3$,$C_0$ and $C_1$), and the familiar $k-C^*$/N-tank-in-series design relationships used for screening and sizing of treatment wetlands. DMSTA was conceptualized as a dynamic extension of a steady state settling rate model. In practice, DMSTA maintains dynamic water balance, hydraulic routing and P cycling routines. Generally, P removal is proportional to the product of water column P and available P storage in vegetation and sediment. Meanwhile, P recycling is described mathematically as a quadratic to the liable pool. Finally P removal to permanent burial is 1st order from labile storage. DMSTA has three key rate constants, $K_1$, $K_2$ and $K_3$ that scale P biogeochemistry represented by the model (removal, recycle and burial). These are the principal fitting parameters for calibration to observed data. These parameters were linearly transformed to a different parameter space from $K$, $C_0$ and $C_1$. * $K$ = net settling rate at steady state (m/yr) * $C_0$ = water column concentration at zero labile storage ($\mu g \ L^{-1}$) * $C_1$ = water column concentration when labile storage is at 1 $g \ m^{-2}$ ($\mu g \ L^{-1}$) Typically, $K$ is estimated by fitting a model to a concentration time-series, $C_0$ and $C_1$ are estimated from the storage and concentration relationship for a given wetlands and/or vegetative community.
## The Models: 1. The $NKC^*$ Model: A generalized tank-in-series outflow model represented as $$C_{out} = \frac{C_{in} - C^{*}}{(1 + \frac{k}{N \cdot HLR})^{N}} + C^*$$ ```{r, echo=FALSE} data.frame( Variable = c("$Q$","$A$","$HLR$","$C_{in}$,$C_{out}$","$C^*$","$k$","$N$"), Definition = c("Flow","Area","Hydraulic Looading Rate $HLR = Q/A$", "Inflow/Outflow Concentration","Background concentration", "areal net settling (removal velocity)", "Number of tanks in series"), Units = c( "$\\mathrm{m^{3}\\ y^{-1}}$", "$\\mathrm{m^{2}}$","$\\mathrm{m\\ y^{-1}}$", "$\\mathrm{mg\\ m^{-3}\\ or \\ \\mu g\\ L^{-1}}$", "$\\mathrm{mg\\ m^{-3}\\ or \\ \\mu g\\ L^{-1}}$", "$\\mathrm{m\\ y^{-1}}$","unitless" ) )|> kable( caption = "Model variables", align = "llc" )|> kable_styling( full_width = FALSE ) ``` When $N = 1$ it indicates a fully mixed tank (conservative), meanwhile $N = 3 - 5$ is the common STA design range. Using equation above, here are some useful limited cases and/or simplifications of the model. * $N = 1$ (single Continuously Stirred Tank Reactor [CSTR]) $$C_{out} = C^{*} + \frac{C_{in} - C^{*}}{1 + \frac{k}{N \cdot HLR}}$$ * $N \to\infty$ (plug-flow limit) $$C_{out} = C^{*} + (C_{in} - C^{*})e^{-k / HLR}$$
Using the tank in series equation, here is the inverse relationship
#### **Solve for required HLR (*given target $C_{out}$*)** Rearrange the $NKC^*$ formula: $$\bigg( 1 + \frac{k}{N \cdot HLR} \bigg)^N = \frac{C_{in} - C^*}{C_{out} - C^{*}} $$ $$HLR = \frac{k/N}{\big( \frac{C_{in} - C^*}{C_{out} - C^{*}} \big)^{1/N} -1}$$ Then area for a specified flow $Q$ is: $$A = \frac{Q}{HLR}$$ **Domain constraints:** you need $C_{out} > C^{*}$ and $C_{in} > C^{*}$. If $C_{out} \le C^{*}$, the outflow is exceeding the assumed background concentration.
#### **Solve for k (given observed $C_{in}$,$C_{out}$ and $HLR$)** $$k = N \cdot HLR \bigg[ \bigg( \frac{C_{in} - C^{*}}{C_{out} - C^{*}} \bigg)^{1/N} -1 \bigg]$$ This is the equivalent settling velocity ($m \ yr^{-1}$) implied by observed performance (at the chosen $N$ and $C^{*8*}). * $N = 1$ (single Continuously Stirred Tank Reactor [CSTR]) $$k = HLR \cdot \bigg( \frac{C_{in}-C^{*}}{C_{out}-C^{*}} -1 \bigg)$$ * $N \to\infty$ (plug-flow limit) $$k = HLR \cdot ln\bigg( \frac{C_{in}-C^{*}}{C_{out}-C^{*}} \bigg)$$ This is often the method to estimate the upper bounds of $k$ from observed annual data.
2. DMSTA Model: Conceptually represented as ```{r DMSTA plot, echo = FALSE,fig.width = 6.5,fig.height = 7,fig.align="center"} oldpar <- par(no.readonly = TRUE) par(mar = c(0.5,0.5,0.5,0.5)) layout(matrix(1:2,1,2),widths = c(0.2,1)) plot(0:1,0:1,type="n",ann=F,axes=F) text(0.1, 0.95, "One CSTR at Steady-State", adj = c(0, 1), font = 3, cex = 1.1,xpd=NA) text(0.1, 0.89, "Unit Area Storage & Fluxes", adj = c(0, 1), font = 3, cex = 0.9,xpd=NA) text(0.1, 0.85, "Concs in mg/m3", adj = c(0, 1), font = 3, cex = 0.8,xpd=NA) text(0.1, 0.82, "Fluxes in mg/m2-yr", adj = c(0, 1), font = 3, cex = 0.8,xpd=NA) text(0.1, 0.79, "Storage in mg/m2", adj = c(0, 1), font = 3, cex = 0.8,xpd=NA) plot.new() plot.window(xlim = c(0, 10), ylim = c(0, 9), asp = 1) wc <- list(x1 = 3.2, x2 = 9.2, y1 = 6.1, y2 = 8.5) rect(wc$x1, wc$y1, wc$x2, wc$y2, col = "#C8F7F7", border = "black", lwd = 1) text((wc$x1+wc$x2)/2, 7.9, "Water Column", cex = 1.0, font = 2) text((wc$x1+wc$x2)/2, 7.35, "Mass = M", cex = 0.95, font = 2) text((wc$x1+wc$x2)/2, 6.85, "Conc = C = M / Z", cex = 0.95, font = 2) # Biomass box bio <- list(x1 = 3.2, x2 = 9.2, y1 = 2.2, y2 = 4.6) rect(bio$x1, bio$y1, bio$x2, bio$y2, col = "#CFF7C8", border = "black", lwd = 1) text((bio$x1+bio$x2)/2, 3.8, "Biomass P Storage", cex = 1.0, font = 2) text((bio$x1+bio$x2)/2, 3.25, "S", cex = 1.0, font = 2) # --- Arrows & labels --- # Inflow L -> into water column (left) arrows(1.4, 7.3, wc$x1, 7.3, length = 0.1, lwd = 1) text(2.25, 7.55, "L", cex = 1.1, font = 2) # Outflow Q C -> out of water column (right) arrows(wc$x2, 7.3, 10.5, 7.3, length = 0.1, lwd = 1,xpd=NA) text(10, 7.55, "Q C", cex = 1.0, font = 2) # Downward arrow: water column -> biomass (uptake) arrows(5.0, wc$y1, 5.0, bio$y2, length = 0.1, lwd = 1) # Label left of the downward arrow: Fz K1 S C text(3.05, 5.35, expression(F[z]~K[1]~S~C), adj = c(0, 0.5), cex = 0.95, font = 2) # Upward arrow: biomass -> water column (recycle) arrows(7.4, bio$y2, 7.4, wc$y1, length = 0.1, lwd = 1) # Label to the right: K2 S^2 text(8.25, 5.35, expression(K[2]~S^2), adj = c(0, 0.5), cex = 0.95, font = 2) # Downward arrow: biomass -> sink/burial (K3 S) arrows((bio$x1+bio$x2)/2, bio$y1, (bio$x1+bio$x2)/2, 1.0, length = 0.1, lwd = 1) text(7.0, 1.55, expression(K[3]~S), cex = 0.95, font = 2) # Fz definition under the left flux label text(0, 4.4, expression(F[z] == Min(1,~Z/Z[x])), adj = c(0, 0.5), cex = 0.95, font = 2) par(oldpar) ``` ```{r, echo=FALSE} params <- data.frame( Symbol = c("M", "S", "Z", "L", "Q", "K1", "K2", "K3"), Description = c( "Water column P storage", "Biomass P storage", "Mean water depth", "External P load", "Outflow rate", "Maximum uptake rate", "Recycle rate", "Burial rate" ), Units = c( "$\\mathrm{mg\\ m^{-2}}$", "$\\mathrm{mg\\ m^{-2}}$", "$m$", "$\\mathrm{mg\\ m^{-2}\\ yr^{-1}}$", "$\\mathrm{m\\ yr^{-1}}$", "$\\mathrm{m^{3}\\ mg^{-1}\\ yr^{-1}}$", "$\\mathrm{m^{2}\\ mg^{-1}\\ yr^{-1}}$", "$\\mathrm{yr^{-1}}$" ) ) kable( params, caption = "DMSTA State Variables and Parameters", align = "lll" )|> kable_styling( full_width = FALSE )|> pack_rows("State Variables", start_row = 1, end_row = 3) |> pack_rows("Driving Variables", start_row = 4, end_row = 5) |> pack_rows("Parameter Variables", start_row = 6, end_row = 8) ``` ```{r, echo=FALSE} data.frame(Compartment = c("Storage:","Overall:", "Solution for C:","Solution for S:"), Equation = c( "$K1 \\cdot C = K2 \\cdot S + K3$", "$L - Q \\cdot C = K3 \\cdot S$", "$C = \\frac{(K2 \\cdot L + K3^{2})}{K3 \\cdot K1 + Q \\cdot K2}$", "$S = \\frac{(K1 \\cdot C - K3)}{K2}$") )|> kable( caption = "Steady-State Mass Balances 1 CSTR & Fz = 1", align = "lc" )|> kable_styling( full_width = FALSE ) ``` Whereas the steady-state DMSTA solution is equivalent to a simple $KC^{*}$ model with $C^{*} = C_0$ and $K$ is interpreted as a net settling rate ($m \ yr^{-1}$). Therefore, for "equivalent design model" purposes $C^{*} \equiv C_{0}$ and $k \equiv K$. This means that the DMSTAs calibrated ($K$ and $C_{0}$) can be subsituted directly into the $NKC^{*}$ model. Where $$C = \frac{(L + K \cdot C_{0})}{(K + Q)}$$ $$S = 1000 \cdot \frac{C-C_0}{C1 - C_0}$$ ```{r DMSTA plot2, echo = FALSE,out.width = "50%",fig.align="center"} oldpar <- par(no.readonly = TRUE) par(mar=c(0,0,0,0),oma = c(0.1,0.1,0.1,0.1)) plot.new() plot(0:1,0:1,xlim = c(0, 1), ylim = c(0,1), ann=F,axes=F,type="n") wc <- list(x1 = 0.2, x2 = 0.8, y1 = 0.4, y2 = 0.8) rect(wc$x1, wc$y1, wc$x2, wc$y2, col = "#C8F7F7", border = "black", lwd = 1) text(wc$x1+(wc$x2-wc$x1)/2, wc$y1+(wc$y2-wc$y1)/2, "C", cex = 0.95, font = 2) a1 <- list(x0 = 0, y0=wc$y1+(wc$y2-wc$y1)/2, x1 = wc$x1, y1 = wc$y1+(wc$y2-wc$y1)/2) with(a1,arrows(x0,y0, x1,y1, length = 0.1, lwd = 1)) text(a1$x0+(a1$x1-a1$x0)/2, a1$y0, "L", cex = 1, font = 2,pos=3,offset = 0.25) a2 <- list(x0 = wc$x2, y0 = wc$y1+(wc$y2-wc$y1)/2, x1 = 1, y1 = wc$y1+(wc$y2-wc$y1)/2) with(a2,arrows(x0,y0, x1,y1, length = 0.1, lwd = 1)) text(a2$x0+(a2$x1-a2$x0)/2, a2$y0, "Q C", cex = 1.0, font = 2,pos=3,offset = 0.25) a3 <- list(x0 = wc$x1+(wc$x2-wc$x1)/2,y0 = wc$y1, x1 = wc$x1+(wc$x2-wc$x1)/2, y1 = wc$y1*0.75) with(a3,arrows(x0,y0, x1,y1, length = 0.1, lwd = 1)) text(a3$x0, a3$y1, "K (C - C0)", cex = 1, font = 2,pos=1,offset = 0.25) par(oldpar) ``` Where, $$K = \frac{K2 \cdot K1}{K2}$$ Therefore, given ($K$,$C_0$,$C_1$), ($K1$, $K2$, $K3$) can be estimated: $$K3 = K \cdot \frac{C_{1} - C_{0}}{1000}$$ $$K1 = \frac{K3}{C_0}$$ $$K_2 = \frac{K3 \cdot K1}{K}$$ Given ($K1$, $K2$, $K3$), ($K$,$C_0$,$C_1$) can be estimated: $$C_{0} = \frac{K_3}{K_1}$$ $$K = \frac{K_1 \cdot K_3}{K_2}$$ $$ C_1 = \frac{1000 \cdot K_{2} + K_{3}}{K_{1}}$$ ### Helpful R Code To see these equation in practice here are some simple R functions and examples. ```{r} # Forward NKC* (tanks-in-series) prediction nkc_out <- function(Cin, k, HLR, N = 1, Cstar) { Cstar + (Cin - Cstar) / (1 + k / (N * HLR))^N } # Solve for HLR given target Cout nkc_hlr <- function(Cin, Cout, k, N = 1, Cstar) { r <- (Cin - Cstar) / (Cout - Cstar) (k / N) / (r^(1/N) - 1) } # Solve for k given observed Cin/Cout and HLR nkc_k <- function(Cin, Cout, HLR, N = 1, Cstar) { if (any(Cout <= Cstar)) stop("Cout must be greater than Cstar") r <- (Cin - Cstar) / (Cout - Cstar) N * HLR * (r^(1/N) - 1) } # DMSTA parameter translations dmsta_KC_to_K123 <- function(K, C0, C1) { K3 <- K * (C1 - C0) / 1000 K1 <- K3 / C0 K2 <- K3 * K1 / K list(K1 = K1, K2 = K2, K3 = K3) } dmsta_K123_to_KC <- function(K1, K2, K3) { C0 <- K3 / K1 K <- (K1 * K3) / K2 C1 <- (1000 * K2 + K3) / K1 list(K = K, C0 = C0, C1 = C1) } # Estimate k from plug and flow estimate_k_plug <- function( Cin, Cout, HLR, Cstar ) { if (any(Cout <= Cstar)) stop("Cout must be greater than Cstar") HLR * log( (Cin - Cstar) / (Cout - Cstar) ) } ``` ```{r,include = FALSE,eval = FALSE} ## Estimate k from CSTR estimate_k_CSTR <- function( Cin, Cout, HLR, N = 1, Cstar ) { if (any(Cout <= Cstar)) stop("Cout must be greater than Cstar") N * HLR * (( (Cin - Cstar) / (Cout - Cstar) )^(1/N) - 1) } ``` ```{r} ## Example Data Cin <- 122 Cout <- 17 k <- 66.5 HLR <- 9.5 Cstar <- 2 K <- 16.8 C0 <- 2 C1 <- 22 K1 <- 0.168 K2 <- 0.00336 K3 <- 0.336 # Estimated Cout given Cin, k, & HLR nkc_out(Cin, k, HLR, N = 1, Cstar) # Estimated HLR given Cin, Cout & k nkc_hlr(Cin,Cout,k,N = 1,Cstar) # vs more tanks nkc_hlr(Cin,Cout,k,N = 3,Cstar) ## Estimate k nkc_k(Cin,Cout,HLR,1,Cstar) # estimate model parameters dmsta_KC_to_K123(K,C0,C1) dmsta_K123_to_KC(K1,K2,K3) ```