# Selecting an optimal tank volume

Choosing the best size for a rain tank can be challenging.   Larger rain tanks provide a more reliable supply but the incremental improvement in reliability, for an increase in volume, gets smaller as the tank gets larger.  A typical plot of reliability against tank volume looks like the figure below.

Figure 1 Supply reliability as a function of rain tank size

There has been suggestions that one way to select a tank size is where the slope of the curve (figure 1) is equal to 1, the idea being that this point represents a reasonable trade-off between tank size and reliability.  Often the point where the tangent has a slope of 1 will be selected by eye.

The approach is generally not reliable or consistent because the selected volume is greatly dependent on the scales chosen for the axes, as shown in figure 2 below.  In all these examples, which use the same data, the slope of the tangent appears to be equal to one yet the ‘optimal’ volumes range from 28.1kL to 72.1 kL.  Similar variability occurs if the aspect ratio of the graphs is changed even when the axes limits remain constant (Figure 3)

A similar issue was identified by Gippel and Stewardson (1998) in their work on environmental flows.

Figure 2 Selecting the ‘optimal’ tank volume as the point where the slope is equal to one is unreliable as the result depends on the axes scaling

Figure 3 Even when the axes limits are constant, the shape of the graph determines when the tangent appears to have a slope of one

An alternative approach is to calculate the tank volume where the derivative is equal to one. This can either be done by calculating the numerical derivative using the volume/reliability data, or by fitting an analytic curve through the data points and then differentiating.  For the data used in these examples, the corresponding volume, for a derivative of 1 is 18.3 kL i.e. none of the 6 curves above produced the correct result.

Gippel and Stewardson also suggested using the point on the curve where the absolute value of the curvature is the greatest. The curvature is defined as follows.

$\kappa = \frac{ \frac{d^2y}{dx^2}}{\left (1 + \left( \frac{dy}{dx}\right)^2 \right)^{3/2}}$

For the data in these examples the maximum of the absolute value of the curvature occurs at 25 kL.

There are also a range of approaches to selecting an optimal tank volume based on issues other than reliability.  These include objectives related to:

• Runoff frequency
• Flow volume
• Initial loss

In each case, the aim is to use the rain tank to make the behaviour of runoff leaving a developed site more like it would have been in the pre-developed condition.  For example, in Melbourne’s east, surface runoff to a stream occurs about 12 times per year under natural conditions. This increases to around 120 times per year for a developed catchment with impervious surfaces.  Harvesting water with a rain tank can help reduce runoff frequency.

R code for this blog.

library(sfsmisc)
library(plotrix)

# a function to approximate relationship between tank volume and reliability
# The function is used to produce some example data. Usually volume/reliability data would
# come from a model of a roof/tank system.

CalcReliability <- function(vol) {

100*(1-0.5*exp(-0.05*vol))

}

# plot the graph of reliability against tank volume
x <- vol.seq <- seq(1,100,1)
y <- CalcReliability(vol.seq)

plot(x,y,
ylab='reliability (%)',
xlab='tank volume (kL)',
las=1,
type='l',
lwd=2)

# the slope that looks like it has a value of one on this graph
# can be calculated from the user coordinates and the pin coordinates
# pin - current plot dimensions, (width, height) in inches
# usr - A vector of the form c(x1, x2, y1, y2) giving the extremes of the coordinates of the plotting region.

my.pin <- par('pin')
my.usr <- par('usr')

# The slope that looks like a slope of 1 will be:

my.slope <- my.pin[1]/(my.usr[2]-my.usr[1]) *((my.usr[4]-my.usr[3])/my.pin[2] )

# The optimum tank volume will appear to be when the derivative of the reliability
# curve is equal to my.slope

CalcOptTankVolume <- function(tank.volume) {D1ss(vol.seq,CalcReliability(vol.seq), tank.volume, spar.offset = 0) - my.slope}

# The optimal tank volume will be when the function CalcOptTankVolume = 0

tank.volume.opt <- uniroot(CalcOptTankVolume, lower=0, upper=100)
tank.volume.opt
$root # [1] 33.13004 kL # add the optimal point to graph x.value <- tank.volume.opt$root
y.value <- CalcReliability(x.value)
points(x.value, y.value, pch=19, cex=1.5, col='blue' )

# draw in the tangent
my.intercept <- y.value - my.slope*x.value
x.span <- my.usr[2] - my.usr[1]
ablineclip(my.intercept, my.slope, x1=x.value-0.2*x.span, x2=x.value+0.2*x.span)

# As a function

OptimPlot <- function(x.max, x.min, y.max, y.min, tank.volume, tank.reliability){
# x.max and x.min specify the limits of the x-axis
# y.max and y.min specify the limits of the y-axis
# tank.volume and tank.reliability are vectors of values
# probably from a simulation model of the tank system

plot(tank.volume,tank.reliability,
ylab='reliability (%)',
xlab='tank volume (kL)',
las=1,
type='l',
lwd=2,
xlim = c(x.min,x.max),
ylim = c(y.min, y.max))

my.pin <- par('pin')
my.usr <- par('usr')

# The slope that looks like a slope of 1 will be:

my.slope <- my.pin[1]/(my.usr[2]-my.usr[1]) *((my.usr[4]-my.usr[3])/my.pin[2] )

CalcOptTankVolume <- function(tank.volume.trial) {D1ss(tank.volume,tank.reliability, tank.volume.trial, spar.offset = 0) - my.slope}

tank.volume.opt <- uniroot(CalcOptTankVolume, lower=0, upper=100)

# add the optimal point to graph
x.value <- tank.volume.opt$root y.value <- CalcReliability(x.value) points(x.value, y.value, pch=19, cex=1.5, col='blue' ) # draw in the tangent my.intercept <- y.value - my.slope*x.value x.span <- my.usr[2] - my.usr[1] ablineclip(my.intercept, my.slope, x1=x.value-0.2*x.span, x2=x.value+0.2*x.span) segments(x0 = x.value, y0 = y.value, x1 = x.value, y1 = 0, lty=2 ) text(x.value, y.min, labels=paste(round(x.value, 1), 'kL'), adj=0, cex=0.9, pos=4, offset=0.5) return(tank.volume.opt$root)

}

# Produce the graphs in the blog

par(mfrow=c(2,2) )
par(mar = c(2.1,4.1, 2.1, 2.1))

# tank.volume and tank.reliability
# would usually be a vector of results from a model
# here we make up some values

tank.volume <- seq(1,100,1)
tank.reliability <- CalcReliability(tank.volume)

OptimPlot(x.min=0, x.max=50, y.min=50, y.max=100, tank.volume, tank.reliability)
OptimPlot(x.min=0, x.max=100, y.min=50, y.max=100, tank.volume, tank.reliability)
OptimPlot(x.min=10, x.max=100, y.min=70, y.max=100, tank.volume, tank.reliability)
OptimPlot(x.min=10, x.max=100, y.min=90, y.max=100, tank.volume, tank.reliability)

OptimPlot(x.min=0, x.max=100, y.min=50, y.max=100, tank.volume, tank.reliability)

# Where is the derivative equal to one

CalcOptTankVolumeS1 <- function(tank.volume.trial) {D1ss(tank.volume,tank.reliability, tank.volume.trial, spar.offset = 0) - 1}
tank.volume.opt.S1 <- uniroot(CalcOptTankVolumeS1, lower=0, upper=100)
tank.volume.opt.S1
# $root # [1] 18.326 # Curvature # Gippel and Stewardson, 1998 suggest the point of maximum abs(curvature) may be used. tank.volume <- seq(1,100,1) tank.reliability <- CalcReliability(tank.volume) D2 <- D2ss(tank.volume,tank.reliability, tank.volume, spar.offset = 0)$y
D1 <- D1ss(tank.volume,tank.reliability, tank.volume, spar.offset = 0)

my.curvature <- abs(D2/(1+D1^2)^(3/2))

plot(tank.volume, my.curvature )

# Where is the abs(my.curvature) a maximum?
which.max(my.curvature )
tank.volume[which.max(my.curvature )]
# 25