Rating curve fitting

The following power-law relationship is used to model the level $(h)$ and discharge $(q)$ relationship:

\[q = a \, (h-b)^c;\]

where $a>0$, $b \in \mathbb{R}$ and $c>0$ are the parameters. The function rcfit finds the parameter estimates for a set of gaugings.

Algorithm

In the log space, the level-discharge relationship can be written as follows:

\[\log q = \log a + c \log (h-b),\]

which is almost a simple linear regression model.

With a set of $n \geq 3$ gaugings $\{(h_i,q_i), 1 \leq i \leq n\}$, the optimal parameters $(a,b,c)$ are defined by the ones that minimize the sum of squares in the log space expressed as the following objective function:

\[f_{obj}(a,b,c) = \sum_{i=1}^n \left\{ \log q_i - \log a - c \log (h_i-b) \right\}^2.\]

Conditional on $b$, the parameters $a$ and $c$ that minimize the sum of squares are the estimated linear regression coefficients. The search for the optimal $\left( -\infty < b < \min(h_i) \right)$ is performed with the limited-memory Broyden–Fletcher–Goldfarb–Shanno algorithm (L-BFGS). For each of the candidates for $b$, the optimal values of $a$ and $c$ are calculated explicitly with the normal equations in linear regression.

Fit on gaugings

The function rcfit can be called with the gauging levels and discharges:

julia> rc = rcfit(h,q)RatingCurve
   data: Vector{Gauging}[194]
   Parameters:
      a = 76.38924950581774
      b = 26.54581500260024
      c = 1.653076883262162

The function returns a RatingCurve type.

Note

The function rcfit can also be called with the vector of Gauging as argument.

The quality of the fit can be visually assessed in the log space:

x = log.(h .- rc.b)
y = log.(q)

plot(x=x, y=y, Geom.point, intercept=[log(rc.a)], slope=[rc.c], Geom.abline(color="red", style=:dash))
x -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 -6.0 -5.5 -5.0 -4.5 -4.0 -3.5 -3.0 -2.5 -2.0 -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 -6 -3 0 3 6 -6.0 -5.8 -5.6 -5.4 -5.2 -5.0 -4.8 -4.6 -4.4 -4.2 -4.0 -3.8 -3.6 -3.4 -3.2 -3.0 -2.8 -2.6 -2.4 -2.2 -2.0 -1.8 -1.6 -1.4 -1.2 -1.0 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.2 5.4 5.6 5.8 6.0 h,j,k,l,arrows,drag to pan i,o,+,-,scroll,shift-drag to zoom r,dbl-click to reset c for coordinates ? for help ? -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 -3.0 -2.5 -2.0 -1.5 -1.0 -0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 10.5 11.0 11.5 12.0 -5 0 5 10 15 -3.0 -2.8 -2.6 -2.4 -2.2 -2.0 -1.8 -1.6 -1.4 -1.2 -1.0 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.2 5.4 5.6 5.8 6.0 6.2 6.4 6.6 6.8 7.0 7.2 7.4 7.6 7.8 8.0 8.2 8.4 8.6 8.8 9.0 9.2 9.4 9.6 9.8 10.0 10.2 10.4 10.6 10.8 11.0 11.2 11.4 11.6 11.8 12.0 y

and in the original space:

obs = layer(data, x=:Level, y=:Discharge, Geom.point)
model = layer(h->discharge(rc, h), 26, 32, Theme(default_color=colorant"red"))
plot(obs, model)
Level 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 20.0 20.5 21.0 21.5 22.0 22.5 23.0 23.5 24.0 24.5 25.0 25.5 26.0 26.5 27.0 27.5 28.0 28.5 29.0 29.5 30.0 30.5 31.0 31.5 32.0 32.5 33.0 33.5 34.0 34.5 35.0 35.5 36.0 36.5 37.0 37.5 38.0 20 25 30 35 40 20.0 20.2 20.4 20.6 20.8 21.0 21.2 21.4 21.6 21.8 22.0 22.2 22.4 22.6 22.8 23.0 23.2 23.4 23.6 23.8 24.0 24.2 24.4 24.6 24.8 25.0 25.2 25.4 25.6 25.8 26.0 26.2 26.4 26.6 26.8 27.0 27.2 27.4 27.6 27.8 28.0 28.2 28.4 28.6 28.8 29.0 29.2 29.4 29.6 29.8 30.0 30.2 30.4 30.6 30.8 31.0 31.2 31.4 31.6 31.8 32.0 32.2 32.4 32.6 32.8 33.0 33.2 33.4 33.6 33.8 34.0 34.2 34.4 34.6 34.8 35.0 35.2 35.4 35.6 35.8 36.0 36.2 36.4 36.6 36.8 37.0 37.2 37.4 37.6 37.8 38.0 h,j,k,l,arrows,drag to pan i,o,+,-,scroll,shift-drag to zoom r,dbl-click to reset c for coordinates ? for help ? -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 -2000 0 2000 4000 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 Discharge

Discharge estimation

With the fitted rating curve, the discharge estimation at the level $h_0 = 29$ can be ontained with discharge:

julia> discharge(rc, 29)336.9604910589399

It is also possible to estimate the level corresponding to the discharge $q = 340$ with level:

julia> level(rc, 340)29.013368029435256

Parameter uncertainty

The rating curve parameter uncertainty can be estimated by bootstrap with the function cint. For the Sainte-Anne example, the 95% confidence intervals are estimated using 100 boostrap samples of the original gaugings:

res = cint(rc, nboot=100)
println(string("a ∈ [", res[1,1]," , ", res[2,1]," ]"))
println(string("b ∈ [", res[1,2]," , ", res[2,2]," ]"))
println(string("c ∈ [", res[1,3]," , ", res[2,3]," ]"))
a ∈ [71.92358482426245 , 77.4028642010117 ]
b ∈ [26.515652625317752 , 26.553307015809576 ]
c ∈ [1.639584870097259 , 1.7091800842278784 ]
Note

In the bootstrap resampling procedure, the gauging with the minimum level is always selected to ensure that the bootstrap rating curves are always defined on the original gauging level range.

Discharge uncertainty

The 95% confidence interval on the discharge estimation at $h_0 = 29$ can be obtained with pint:

julia> pint(rc, 29)2-element Vector{Float64}:
 303.1402671607886
 374.5539106306123

For more details on how this uncertainty is estimated, see the description of pint.

The confidence interval for the whole level range of the rating curve can be plotted as follows:

h₀ = range(minimum(data.Level), stop=32, length=100)

# Discharge estimation for each level
q̂₀ = discharge.(rc, h₀)

# 95% confidence intervals of each discharge estimation
res = pint.(rc, h₀)

# Lower bound of interval
qmin = getindex.(res,1)

# Upper bound of interval
qmax = getindex.(res,2)

# Plotting the interval and the gaugings
obs = layer(data, x=:Level, y=:Discharge, Geom.point)
model = layer(x=h₀, y=q̂₀, Geom.line,
    ymin = qmin, ymax = qmax, Geom.ribbon,
    Theme(default_color=colorant"red"))

plot(obs, model)
Level 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 20.0 20.5 21.0 21.5 22.0 22.5 23.0 23.5 24.0 24.5 25.0 25.5 26.0 26.5 27.0 27.5 28.0 28.5 29.0 29.5 30.0 30.5 31.0 31.5 32.0 32.5 33.0 33.5 34.0 34.5 35.0 35.5 36.0 36.5 37.0 37.5 38.0 20 25 30 35 40 20.0 20.2 20.4 20.6 20.8 21.0 21.2 21.4 21.6 21.8 22.0 22.2 22.4 22.6 22.8 23.0 23.2 23.4 23.6 23.8 24.0 24.2 24.4 24.6 24.8 25.0 25.2 25.4 25.6 25.8 26.0 26.2 26.4 26.6 26.8 27.0 27.2 27.4 27.6 27.8 28.0 28.2 28.4 28.6 28.8 29.0 29.2 29.4 29.6 29.8 30.0 30.2 30.4 30.6 30.8 31.0 31.2 31.4 31.6 31.8 32.0 32.2 32.4 32.6 32.8 33.0 33.2 33.4 33.6 33.8 34.0 34.2 34.4 34.6 34.8 35.0 35.2 35.4 35.6 35.8 36.0 36.2 36.4 36.6 36.8 37.0 37.2 37.4 37.6 37.8 38.0 h,j,k,l,arrows,drag to pan i,o,+,-,scroll,shift-drag to zoom r,dbl-click to reset c for coordinates ? for help ? -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 -2000 0 2000 4000 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 Discharge

Fit quality assessment

The BIC (Bayesian Information Criterion) is an index of the quality of the curve fit to the gaugings. Assuming that the errors are normally distributed in the log space, the BIC of the curve in log space can be obtained as follows:

$\operatorname{bic} = n \log \hat\sigma_e^2 + 3 \log n,$

where $n$ corresponds to the number of gaugings, $\hat\sigma_e^2$ corresponds to the variance of the errors in the log space and the value 3 stands for the number of parameters.

The BIC of a fitted rating curve can be obtained with the function bic:

julia> bic(rc)-1168.4783580168496

Constrained rating curve fit

The rating curve can also be fit to the gaugings by requiring the curve to pass through a particular point. The curve obtained is the one that minimizes the sum of the squared errors among the curves that pass through the given point.

For example, if one wants the curve to pass through the last gauging, this constraint can be added by using rcfit with the contraint arguement:

# Extract the level and discharge of the last gauging
Gₘ = maximum(G)
h̃ = level(Gₘ)
q̃ = discharge(Gₘ)

# Fit the constrained curve
constrained_rc = rcfit(G, [h̃, q̃])
RatingCurve
   data: Vector{Gauging}[194]
   Parameters:
      a = 80.73298733291215
      b = 26.57624202035966
      c = 1.5708653243496047

The constrained curve can be plotted in the usual way:

obs = layer(data, x=:Level, y=:Discharge, Geom.point)
model = layer(h->discharge(constrained_rc, h), 26, 32, Theme(default_color=colorant"red"))
plot(obs, model)
Level 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 20.0 20.5 21.0 21.5 22.0 22.5 23.0 23.5 24.0 24.5 25.0 25.5 26.0 26.5 27.0 27.5 28.0 28.5 29.0 29.5 30.0 30.5 31.0 31.5 32.0 32.5 33.0 33.5 34.0 34.5 35.0 35.5 36.0 36.5 37.0 37.5 38.0 20 25 30 35 40 20.0 20.2 20.4 20.6 20.8 21.0 21.2 21.4 21.6 21.8 22.0 22.2 22.4 22.6 22.8 23.0 23.2 23.4 23.6 23.8 24.0 24.2 24.4 24.6 24.8 25.0 25.2 25.4 25.6 25.8 26.0 26.2 26.4 26.6 26.8 27.0 27.2 27.4 27.6 27.8 28.0 28.2 28.4 28.6 28.8 29.0 29.2 29.4 29.6 29.8 30.0 30.2 30.4 30.6 30.8 31.0 31.2 31.4 31.6 31.8 32.0 32.2 32.4 32.6 32.8 33.0 33.2 33.4 33.6 33.8 34.0 34.2 34.4 34.6 34.8 35.0 35.2 35.4 35.6 35.8 36.0 36.2 36.4 36.6 36.8 37.0 37.2 37.4 37.6 37.8 38.0 h,j,k,l,arrows,drag to pan i,o,+,-,scroll,shift-drag to zoom r,dbl-click to reset c for coordinates ? for help ? -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 -2000 0 2000 4000 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 Discharge

The confidence interval on the discharge estimations can also be obtained in the usual way:

h₀ = range(minimum(data.Level), stop=32, length=100)

# Discharge estimation for each level
q̂₀ = discharge.(constrained_rc, h₀)

# 95% confidence intervals of each discharge estimation
res = pint.(constrained_rc, h₀)

# Lower bound of interval
qmin = getindex.(res,1)

# Upper bound of interval
qmax = getindex.(res,2)

# Plotting the interval and the gaugings
obs = layer(data, x=:Level, y=:Discharge, Geom.point)
model = layer(x=h₀, y=q̂₀, Geom.line,
    ymin = qmin, ymax = qmax, Geom.ribbon,
    Theme(default_color=colorant"red"))

plot(obs, model)
Level 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 20.0 20.5 21.0 21.5 22.0 22.5 23.0 23.5 24.0 24.5 25.0 25.5 26.0 26.5 27.0 27.5 28.0 28.5 29.0 29.5 30.0 30.5 31.0 31.5 32.0 32.5 33.0 33.5 34.0 34.5 35.0 35.5 36.0 36.5 37.0 37.5 38.0 20 25 30 35 40 20.0 20.2 20.4 20.6 20.8 21.0 21.2 21.4 21.6 21.8 22.0 22.2 22.4 22.6 22.8 23.0 23.2 23.4 23.6 23.8 24.0 24.2 24.4 24.6 24.8 25.0 25.2 25.4 25.6 25.8 26.0 26.2 26.4 26.6 26.8 27.0 27.2 27.4 27.6 27.8 28.0 28.2 28.4 28.6 28.8 29.0 29.2 29.4 29.6 29.8 30.0 30.2 30.4 30.6 30.8 31.0 31.2 31.4 31.6 31.8 32.0 32.2 32.4 32.6 32.8 33.0 33.2 33.4 33.6 33.8 34.0 34.2 34.4 34.6 34.8 35.0 35.2 35.4 35.6 35.8 36.0 36.2 36.4 36.6 36.8 37.0 37.2 37.4 37.6 37.8 38.0 h,j,k,l,arrows,drag to pan i,o,+,-,scroll,shift-drag to zoom r,dbl-click to reset c for coordinates ? for help ? -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 -2000 0 2000 4000 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 Discharge