---
title: "Chapter 3: Semi-distributed hydrological modeling"
output: rmarkdown::html_vignette
author: "Ezequiel Toum"
date: "`r Sys.Date()`"
vignette: >
  %\VignetteIndexEntry{Chapter 3: Semi-distributed hydrological modeling}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
bibliography: references.bib
---

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

## The problem

Now we move forward to a semi-distributed catchment case. This means that we are conceiving the
basin as a set of homogeneous polygons that are selected by some criteria; in any basin
the hydrologist is faced with a variety of geology, soils, vegetation, land use and topographic
characteristics that affects the precipitation-runoff generation. One possible solution to deal 
with such a complexity is to consider that there are some sectors that behave 
(e.g.: in terms of runoff generation) in a similar way, hence we can split the basin in what 
the modeler can consider as hydrological *homogeneous* areas. As you can imagine, the criteria
is not unique and depends on many factors: modeling objectives, knowledge about the runoff 
generation processes in the catchment, available input data and numerical models, among others 
[@beven:2012]. 

In this case study we are going to work on a perfect fit case (again a synthetic basin). The
catchment has been discretised in elevation bands (keeping in mind a mountain basin case).  
After this vignette is expect that you:

* understand how to construct a semi-distributed HBV hydrological model. 
* calibrate the parameters in order to get a perfect river discharge fit.
* study the effects of changing the snow module parameters in the streamflow discharge.

## Data

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

data("semi_distributed_hbv")

str(semi_distributed_hbv)
```

To get more details about the dataset just type ```?semi_distributed_hbv```

## Building a semi-distributed hydrological model

For this exercise is supposed that we have just one type of vegetation soil and that the runoff
generation is controlled by the snow accumulation and
melting process. As this basin is located in a mountain region, we consider a mean and homogeneous snowpack evolution within a pre-defined elevation range. 

```{r definition, echo = TRUE}
## brief arguments description
  # basin: data frame with the same structure of the data("semi_distributed_hbv) (colnames included).
  # tair: numeric matrix with air temperature inputs. 
  # precip: numeric matrix with precipitation inputs. 
  # pet: numeric matrix with potential eavapotranspiration inputs. 
  # 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.
hydrological_hbv <- function(basin,
                             tair,
                             precip,
                             pet,
                             param_snow,
                             param_soil,
                             param_route,
                             param_tf,
                             init_snow = 20,
                             init_soil = 0,
                             init_routing = c(0, 0, 0) 
                             ){
  n_it <- nrow(basin)

  # create output lists
  snow_module  <- list()
  soil_module  <- list()
  route_module <- list()
  tf_module    <- list()

  # snow and soil module in every elevation band
  for(i in 1:n_it){
    snow_module[[ i ]] <-
      SnowGlacier_HBV(model = 1, inputData = cbind(tair[ , i], precip[ , i]),
                      initCond =  c(init_snow, 2), param = param_snow)
    soil_module[[ i ]] <-
      Soil_HBV(model = 1, inputData = cbind(snow_module[[i]][ , 5] , pet[ , i]),
               initCond = c(init_soil, basin[i, 'rel_area']), param = param_soil )

  } # end for

  # get total soil discharge
  soil_disch <- lapply(X = 1:n_it, FUN = function(x){
    out <- soil_module[[x]][ , 1]
  })
  soil_disch <- Reduce(f = `+`, x = soil_disch)

  # route module
  route_module <- Routing_HBV(model = 1, lake = F, inputData = as.matrix(soil_disch),
                              initCond = init_routing, param = param_route )

  # transfer function
  tf_module <- round(
    UH(model = 1, Qg = route_module[ , 1], param = param_tf), 4
  )

  return(tf_module)


}# end fun
```

As in the **Lumped model** case, this is just a way of constructing an HBV semi-distributed
model but not the only one.

## Calibrating the parameters

In the next lines I will show you how to generate many parameter sets in order  to get close
to the correct one. Remember that we are talking about *the correct parameter set* because 
is a synthetic case. In real world problems this will not be the case. 

> The calibrating issue has been focus of a lot of debate and research in the hydrological modeling 
> field, I recommend the following material for the interested reader:
>
> * A manifesto for the equifinality thesis [@beven:2006].
> * Sensitivity analysis of environmental models: A systematic review with practical workflow
> [@pianosi:2016].
> * Rainfall-runoff modelling [@beven:2012].
> * Environmental Modelling: An Uncertain Future? [@beven:2008].

```{r}
# first we are going to create set the parameter range
  # snow module
snow_range <- rbind(
  sfcf = c(0, 1.5),
  tr   = c(-1, 1),
  tt   = c(0, 3),
  fm   = c(1.5, 4)
)

  # soil module
soil_range <- rbind(
  fc   = c(100, 200),
  lp   = c(0.5, 1),
  beta = c(1, 3)
)

  # routing module (here I will give you the correct values)
routing_range <- rbind(
  k0   = c(0.09, 0.09),
  k1   = c(0.07, 0.07),
  k2   = c(0.05, 0.05),
  uzl  = c(5, 5),
  perc = c(2, 2)
)

  # transfer function module (I will give the correct value)
tf_range <- rbind(
  bmax = c(2.25, 2.25)
)

```

Then we are going to condense the parameter ranges in a matrix,

```{r}
param_range <- 
  rbind(
    snow_range,
    soil_range,
    routing_range,
    tf_range
  )

head(param_range)
```

In the next step we will generate random sets of parameters. Then we will use them 
to run the model and save our goodness of fit function,

```{r}
# set the number of model runs that you want to try
n_run <- 1000

# build the matrix
n_it <- nrow(param_range)

param_sets <- matrix(NA_real_, nrow = n_run, ncol = n_it)

colnames(param_sets) <- rownames(param_range)
for(i in 1:n_it){
  
  param_sets[ , i] <- runif(n = n_run, 
                            min = param_range[i, 1],
                            max = param_range[i, 2]
                            )
    
}

head(param_sets)

```

Finally we run our semi-distributed model,

```{r}
# goodness of fit vector
gof <- c()

# make a loop
for(i in 1:n_run){
  streamflow <- hydrological_hbv(
                             basin = semi_distributed_hbv$basin,
                             tair = semi_distributed_hbv$tair,
                             precip = semi_distributed_hbv$prec,
                             pet = semi_distributed_hbv$pet,
                             param_snow = param_sets[i, rownames(snow_range) ],
                             param_soil = param_sets[i, rownames(soil_range)],
                             param_route = param_sets[i, rownames(routing_range)],
                             param_tf = param_sets[i, rownames(tf_range)]
                             )
  
  gof[i] <- cor(x = streamflow, y = semi_distributed_hbv$qout)
}

param_sets <- cbind(param_sets, gof)

head(param_sets)
```

Is time to extract the parameter set with the maximum **gof** value,

```{r} 
# get the row index
max_gof   <- which.max(param_sets[ , "gof"])

# extract the parameter set
param_opt <- param_sets[max_gof, ]

param_opt
```

Now compare your best parameter set with the ones that I used to generate the catchment 
streamflow output,

* `param_snow = c(sfcf = 1.1, tr = 0, tt = 0, fm = 1.75)`
* `param_soil = c(fc = 150, lp = 0.90, beta = 1.5)`

**Now is your turn**

* Are your best parameter set values far away from the true ones?
* If your previous answer is **yes** is time to take a breath and think, what 
 is happening? Discuss with your partners...
* Maybe is time not only to look at the streamflow discharge but also to the actual
 evapotranspiration and soil moisture series. 


## References
