---
title: "Chapter 2: A lumped model"
output: rmarkdown::html_vignette
author: "Ezequiel Toum"
date: "`r Sys.Date()`"
vignette: >
  %\VignetteIndexEntry{Chapter 2: A lumped model}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
bibliography: references.bib
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## The problem

The following example is what I consider the first step to understand
how **HBV.IANIGLA** works. As a first approach, I propose you to fit 
the streamflow discharge in a synthetic lumped catchment case (in my opinion this
the most simple hydrological modeling case). 
The objectives of this excersice are:

* to introduce the building-up of the model.
* to get a first approach to parameter calibration.
* to fit the streamflow discharge in a perfect match case (this does not occur in real world hydrology).
* to make a sensitivity analysis. 

## Data

Initially, we load the dataset containing all the necessary information to run 
the model: air temperature, precipitation, potential evapotranspiration and the outflow 
of our synthetic catchment:

```{r}
library(HBV.IANIGLA)

data("lumped_hbv")

head(lumped_hbv)
summary(lumped_hbv)

```

## Building the model

This case is about a catchment without glaciers. In the next lines I will help
you to build-up the model line by line. Because is our first excersice, I will provide
you with the correct *initial conditions* and *parameters* in all modules except for
the **Routing_HBV** function. 

Remember that hydrological models are generally build in a top down direction (from
precipitation to streamflow routing - note that most hydrological books are structured
in the same way). 

```{r}
# we first consider the SnowGlacier module to take into account 
# precipitation partioning and the snow accumulation/melting. 
snow_module <-
  SnowGlacier_HBV(model = 1, 
                  inputData = as.matrix( lumped_hbv[ , c('T(ºC)', 'P(mm/d)')] ),
                  initCond = c(20, 2), 
                  param = c(1.20, 1.00, 0.00, 2.5) )

# is always advisable to take a look at the output (unless until you get familiarised 
# with the model) 
head(snow_module)

# now see the function documentation...you should be able to relate the model option
# with the matrix output!
```

Now we will pass the total water produced (rainfall plus snowmelt) to the soil
routine. Note that we will use the potential evapotranspiration series,

```{r}
soil_module <-
  Soil_HBV(model = 1,
           inputData = cbind(snow_module[ , "Total"], lumped_hbv$`PET(mm/d)`),
           initCond = c(100, 1),
           param = c(200, 0.8, 1.15) )

head(soil_module)
```

from this module we can get the actual evapotranspiration, soil moisture and recharge
series. In the next step we incorporate the last variable to the routing function.
Remember that the module's parameters (*param* argument) are not calibrated!

```{r}
routing_module <-
  Routing_HBV(model = 1, 
              lake = F, 
              inputData = as.matrix(soil_module[ , "Rech"]),
              initCond = c(0, 0, 0), 
              param = c(0.9, 0.01, 0.001, 0.5, 0.01) )

head(routing_module)
```

Finally we will apply the transfer function in order to adjust the hydrograph timing,

```{r fig.width = 7 }
tf_module <-
  round( 
    UH(model = 1,
       Qg = routing_module[ , "Qg"],
       param = c(1.5) ),
    2)

# let's plot the "true" and simulated hydrographs
plot( x = lumped_hbv[ , "Date"], 
      y = tf_module,
      type = "l", col = "dodgerblue",
      xlab = "Date", ylab = "q(mm/d)")
lines(x = lumped_hbv[ , "Date"],
      y = lumped_hbv[ , "qout(mm/d)"], 
      col = "red")

# you can get a goodness of fit measurement by, 
cor(x = lumped_hbv[ , "qout(mm/d)"], y = tf_module)

# hint: to use hydrological goodness of fit functions (e.g.: NSE - Nash-Sutcliffe
# Efficiency) you can install the hydroGOF package.
```

As you will note, our HBV model needs some calibration (in he **Routing_HBV**
parameters). 

**Now is your turn**

> change (manually) the corresponding parameter values to get
> a *decent* streamflow simulation. **Help**: the order of magnitude of the actual
>parameters is correct (e.g.: the real value of *K0* is between 0.9 and 0.1 and so on).

**Hints**

> When you are changing the parameters 'by hand' is always advisable to plot the results.

> Pay attention on how sensible is the result to changes in a single parameter.


## The HBV model as a function

In my view, when building an HBV model is always a better idea to construct a
succinct version of it by defining a function. If your are new in the **R**
language I recommend to visit one of the following web pages,

* [R bloggers](https://www.r-bloggers.com/2016/05/how-to-make-a-function-in-r/)
* [Advanced R](https://adv-r.hadley.nz/functions.html?q=func#functions)

Now lets recycle the previous code to create a general purpose function for 
lumped basin cases,

```{r definition, echo = TRUE}
## brief arguments desription
  # basin: data frame with the same structure of the data("lumped_hbv") (colnames included).
  # param_snow: numeric vector with snow module parameters.
  # param_soil: numeric vector with soil moisture parameters.
  # param_routing: numeric vector with the routing parameters.
  # param_tf: numeric vector with the transfer function parameter.
  # init_snow: numeric value with initial snow water equivalent. Default value being 20 mm.
  # init_soil: numeric value with initial soil moisture content. Default value being 100 mm.
  # init_routing: numeric vector with bucket water initial values. Default values are 0 mm.
## output
  # simulated streamflow series.
hbv_lumped <- function(basin, 
                       param_snow,
                       param_soil,
                       param_routing,
                       param_tf, 
                       init_snow = 20, 
                       init_soil = 100,
                       init_routing = c(0, 0, 0)
                       ){
  
  snow_module <-
  SnowGlacier_HBV(model = 1, 
                  inputData = as.matrix( basin[ , c('T(ºC)', 'P(mm/d)')] ),
                  initCond = c(init_snow, 2), 
                  param = param_snow )
  
  soil_module <-
  Soil_HBV(model = 1,
           inputData = cbind(snow_module[ , "Total"], basin$`PET(mm/d)`),
           initCond = c(init_soil, 1),
           param = param_soil )
  
 routing_module <-
  Routing_HBV(model = 1, 
              lake = F, 
              inputData = as.matrix(soil_module[ , "Rech"]),
              initCond = init_routing, 
              param = param_routing )
 
 tf_module <-
     UH(model = 1,
       Qg = routing_module[ , "Qg"],
       param = param_tf )
 
 
 out <- round(tf_module, 2)
 
 return(out)
  
                       }
```

After running this chunk of code you should see the function name in the **Global Environment**, now you can use it. 

**Advice**

> When I use my own functions I usually write them in a separate R file. 
> This helps me to keep my main script clean. To call the function you just 
> run the single code line *source(file = "my_hbv_model.R")* and *voila*!!!...the 
> function is ready to use.

Note that we have defined default values for all the initial conditions. This means 
that when running the function, you don't have to give them a value. For the sake 
of simplicity I gave them the correct values...so as this is our first case example
forget about them and **focus on the other function arguments**. 

**Now is your turn**

* If you didn't understand the logic behind the function is a good idea to take a break (and 
maybe a breath) and revisit the (first) recommended web page [R bloggers](https://www.r-bloggers.com/2016/05/how-to-make-a-function-in-r/). It is advisable
to reproduce and *play* a little bit with the web page examples. **Programming is a necessary condition to learn how to program!**.
* Analyze carefully how the function arguments and modules'
outputs are used by the subsequent HBV functions, and how the function's returning value 
is set. Without looking at the *hbv_lumped* example, can you do it by your own?
* Now suppose that you want to use the **Routing_HBV** model number two: what do you
 need to modify? is the *param_routing* argument of the same length? what about the 
 *init_routing* argument?

## Using the function to run the model

In this section we are going to make something that looks more closely to what 
we should do in real-world problems: work with the arguments of an HBV
function. Imagine what will happen if you decide to build-up a model every time
you work in a modeling problem...it will be too prone error! To avoid pitfall it
would be a better idea to construct a general purpose function. 

In the following lines we are going to use our *hbv_lumped* model to calibrate 
the routing parameters in order to fit the simulated streamflow discharge.

```{r fig.width = 7}
# one of the ideas behind a function is to succinctly express 
# operations that will look complicated or too large. In our 
# case, by modifying (in the same line of arguments) just some
# parameters we obtain the basin discharge

streamflow <- 
  hbv_lumped(basin = lumped_hbv, 
             param_snow = c(1.20, 1.00, 0.00, 2.5),
             param_soil = c(200, 0.8, 1.15),
             param_routing = c(0.2, 0.01, 0.005, 0.75, 0.15),
             param_tf = c(1.5))

plot( x = lumped_hbv[ , "Date"], 
      y = streamflow,
      type = "l", col = "dodgerblue",
      xlab = "Date", ylab = "q(mm/d)")
lines(x = lumped_hbv[ , "Date"],
      y = lumped_hbv[ , "qout(mm/d)"], 
      col = "red")

cor(x = streamflow, y =  lumped_hbv[ , "qout(mm/d)"] )

```

Now you have a compact expression for your modeling problem. Recycling this kind of functions
will also save you a lot of time. Be also aware that you only have to build them **once**!

**Now is your turn**

* if you haven't, get the perfect (or unless an almost perfect) streamflow fit.
* change another parameter(s) and see what happen with the hydrograph. 

## Sensitivity analysis

According to @beven:2008 a sensitivity analysis is:

> the response of a model to a change in a parameter or input variable. 
> This reaction can either be assesed locally in the model space or over a global
> sample of points. 

In this part of our **lumped model vignette**, I will give you the answer to our calibration
problem and we are going to make a sensitivity analysis on them. 

**Rounting_HBV parameters:** ```c(0.1, 0.05, 0.002, 0.9, 0.1)```

One way to look at the sensitivity of a single parameter, is to change its value
in a reasonable range to evaluate some model response(s) (in our case, the basin discharge)
[@pianosi:2016]. In this example our evaluation measurement will be the Pearson correlation
coefficient. 

```{r}
# first we are going to generate a vector with the possible values
# of our parameter. Let's say that we want to evaluate the effect of
# changing the K0 value.

target_param <- seq(from = 0.1, to = 0.9, by = 0.01)

# now we get the number fo required iterations and we create an empty 
# vector to store the evaluation function results
n_it     <- length(target_param)
response <- c()

# finally run our function n_it times and we plot the results
for(i in 1:n_it){
  
  streamflow <- 
  hbv_lumped(basin = lumped_hbv, 
             param_snow = c(1.20, 1.00, 0.00, 2.5),
             param_soil = c(200, 0.8, 1.15),
             param_routing = c(target_param[i], 0.05, 0.002, 0.9, 0.1),
             param_tf = c(1.5))

  response[i] <- cor(x = streamflow, y =  lumped_hbv[ , "qout(mm/d)"] )
  
}

plot(x = target_param, y = response, type = "p",
     ylim = c(0, 1), pch = 20, cex = 0.5)


```

**Now is your turn**

* explore what happen if the target value is another parameter. 
* try not only with the **Routing_HBV** parameters, but with the other modules.
* which model parameters are the most sensible for streamflow discharge?

## References
