CreateInputsModel.GRiwrm.R 16.51 KiB
#' Creation of an InputsModel object for a **airGRiwrm** network
#'
#' @param x \[GRiwrm object\] diagram of the semi-distributed model (See [CreateGRiwrm])
#' @param DatesR [POSIXt] vector of dates
#' @param Precip (optional) [matrix] or [data.frame] of [numeric] containing
#'        precipitation in \[mm per time step\]. Column names correspond to node IDs
#' @param PotEvap (optional) [matrix] or [data.frame] of [numeric] containing
#'        potential evaporation \[mm per time step\]. Column names correspond to node IDs
#' @param Qobs (optional) [matrix] or [data.frame] of [numeric] containing
#'        observed flows. It must be provided only for nodes of type "Direct
#'        injection" and "Diversion". See [CreateGRiwrm] for
#'        details about these node types. Unit is \[mm per time step\] for nodes
#'        with an area, and \[m3 per time step\] for nodes with `area=NA`.
#'        Column names correspond to node IDs. Negative flows are abstracted from
#'        the model and positive flows are injected to the model
#' @param Qmin (optional) [matrix] or [data.frame] of [numeric] containing
#'        minimum flows to let downstream of a node with a Diversion \[m3 per
#'        time step\]. Default is zero. Column names correspond to node IDs
#' @param PrecipScale (optional) named [vector] of [logical] indicating if the
#'        mean of the precipitation interpolated on the elevation layers must be
#'        kept or not, required to create CemaNeige module inputs, default `TRUE`
#'        (the mean of the precipitation is kept to the original value)
#' @param TempMean (optional) [matrix] or [data.frame] of time series of mean
#'        air temperature \[°C\], required to create the CemaNeige module inputs
#' @param TempMin (optional) [matrix] or [data.frame] of time series of minimum
#'        air temperature \[°C\], possibly used to create the CemaNeige module inputs
#' @param TempMax (optional) [matrix] or [data.frame] of time series of maximum
#'        air temperature \[°C\], possibly used to create the CemaNeige module inputs
#' @param ZInputs  (optional) named [vector] of [numeric] giving the mean
#'        elevation of the Precip and Temp series (before extrapolation) \[m\],
#'        possibly used to create the CemaNeige module input
#' @param HypsoData	(optional) [matrix] or [data.frame] containing 101 [numeric]
#'        rows: min, q01 to q99 and max of catchment elevation distribution \[m\],
#'        if not defined a single elevation is used for CemaNeige
#' @param NLayers (optional) named [vector] of [numeric] integer giving the number
#'        of elevation layers requested [-], required to create CemaNeige module
#'        inputs, default=5
#' @param ... used for compatibility with S3 methods
#' @details Meteorological data are needed for the nodes of the network that
#' represent a catchment simulated by a rainfall-runoff model. Instead of
#' [airGR::CreateInputsModel] that has [numeric] [vector] as time series inputs,
#' this function uses [matrix] or [data.frame] with the id of the sub-catchment
#' as column names. For single values (`ZInputs` or `NLayers`), the function
#' requires named [vector] with the id of the sub-catchment as name item. If an
#' argument is optional, only the column or the named item has to be provided.
#' See [airGR::CreateInputsModel] documentation for details concerning each input.
#' Number of rows of `Precip`, `PotEvap`, `Qobs`, `Qmin`, `TempMean`, `TempMin`,
#' `TempMax` must be the same of the length of `DatesR` (each row corresponds to
#' a time step defined in `DatesR`).
#' For example of use of Direct Injection nodes, see vignettes
#' "V03_Open-loop_influenced_flow" and "V04_Closed-loop_regulated_withdrawal".
#' For example of use of Diversion nodes, see example below and vignette
#' "V06_Modelling_regulated_diversion".
#' @return A \emph{GRiwrmInputsModel} object which is a list of \emph{InputsModel}
#' objects created by [airGR::CreateInputsModel] with one item per modeled sub-catchment.
#' @export
#' @example man-examples/RunModel.GRiwrmInputsModel.R
CreateInputsModel.GRiwrm <- function(x, DatesR,
                                     Precip = NULL,
                                     PotEvap = NULL,
                                     Qobs = NULL,
                                     Qmin = NULL,
                                     PrecipScale = TRUE,
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
TempMean = NULL, TempMin = NULL, TempMax = NULL, ZInputs = NULL, HypsoData = NULL, NLayers = 5, ...) { # Check and format inputs varNames <- c("Precip", "PotEvap", "TempMean", "Qobs", "Qmin", "TempMin", "TempMax", "ZInputs", "HypsoData", "NLayers") names(varNames) <- varNames lapply(varNames, function(varName) { v <- get(varName) if (!is.null(v)) { if (is.matrix(v) || is.data.frame(v)) { if (is.null(colnames(v))) { stop(sprintf( "'%s' must have column names", varName )) } else if (!all(colnames(v) %in% x$id)) { stop(sprintf( "'%s' column names must be included in 'id's of the GRiwrm object", varName )) } if (!varName %in% c("ZInputs", "NLayers", "HypsoData") && nrow(v) != length(DatesR)) { stop(sprintf( "'%s' number of rows and the length of 'DatesR' must be equal", varName )) } if (varName %in% c("Precip", "PotEvap", "Qmin")) { if (any(is.na(v))) { stop(sprintf( "`NA` values detected in '%s'. Missing values are not allowed in InputsModel", varName )) } if (any(v < 0)) { stop(sprintf( "'%s' values must be positive or nul. Missing values are not allowed in InputsModel", varName )) } } } else if (!varName %in% c("ZInputs", "NLayers")) { stop(sprintf("'%s' must be a matrix or a data.frame", varName)) } } }) directFlowIds <- x$id[is.na(x$model) | x$model == "Diversion"] if (length(directFlowIds) > 0) { err <- FALSE if (is.null(Qobs)) { err <- TRUE } else { Qobs <- as.matrix(Qobs) if (is.null(colnames(Qobs))) { err <- TRUE } else if (!all(directFlowIds %in% colnames(Qobs))) { err <- TRUE } } if (err) stop(sprintf("'Qobs' column names must at least contain %s", paste(directFlowIds, collapse = ", "))) } if (!all(colnames(Qobs) %in% directFlowIds)) { stop("The following columns in 'Qobs' don't match with ", "Direction Injection or Diversion nodes: ", paste(setdiff(colnames(Qobs), directFlowIds), collapse = ", ")) Qobs <- Qobs[directFlowIds, ] }
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
diversionRows <- getDiversionRows(x) if (length(diversionRows) > 0) { warn <- FALSE if (is.null(Qmin)) { warn <- TRUE } else { Qmin <- as.matrix(Qmin) if (!all(colnames(Qmin) %in% x$id[diversionRows])) { stop(paste( "'Qmin' contains columns that does not match with IDs of Diversion nodes:\n", setdiff(colnames(Qmin), x$id[diversionRows]) )) } if (is.null(colnames(Qmin))) { warn <- TRUE } else if (!all(x$id[diversionRows] %in% colnames(Qmin))) { warn <- TRUE } if (any(is.na(Qmin))) { stop("`NA` values are note allowed in 'Qmin'") } } if (warn) { warning( sprintf( "'Qmin' would include the following columns %s.\n Zero values are applied by default.", paste(directFlowIds, collapse = ", ") ) ) } # Qmin completion for Diversion nodes with default zero values Qmin0 <- matrix(0, nrow = length(DatesR), ncol = length(diversionRows)) colnames(Qmin0) <- x$id[diversionRows] if (is.null(Qmin)) { Qmin <- Qmin0 } else { Qmin0[, colnames(Qmin)] <- Qmin Qmin <- Qmin0 } } InputsModel <- CreateEmptyGRiwrmInputsModel(x) # Qobs completion for at least filling Qupstream of all nodes by zeros Qobs0 <- matrix(0, nrow = length(DatesR), ncol = nrow(x)) colnames(Qobs0) <- x$id if (is.null(Qobs)) { Qobs <- Qobs0 } else { Qobs0[, colnames(Qobs)] <- Qobs Qobs <- Qobs0 } for(id in getNodeRanking(x)) { message("CreateInputsModel.GRiwrm: Processing sub-basin ", id, "...") InputsModel[[id]] <- CreateOneGRiwrmInputsModel(id = id, griwrm = x, DatesR = DatesR, Precip = getInputBV(Precip, id), PrecipScale, PotEvap = getInputBV(PotEvap, id), TempMean = getInputBV(TempMean, id), TempMin = getInputBV(TempMin, id), TempMax = getInputBV(TempMax, id), ZInputs = getInputBV(ZInputs, id), HypsoData = getInputBV(HypsoData, id), NLayers = getInputBV(NLayers, id, 5), Qobs = Qobs,
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
Qmin = getInputBV(Qmin, id) ) } attr(InputsModel, "TimeStep") <- getModelTimeStep(InputsModel) return(InputsModel) } #' Creation of an empty InputsModel object for **airGRiwrm** nodes #' #' @param griwrm a `GRiwrm` object (See [CreateGRiwrm]) #' #' @return \emph{GRiwrmInputsModel} empty object #' @noRd CreateEmptyGRiwrmInputsModel <- function(griwrm) { InputsModel <- list() class(InputsModel) <- c("GRiwrmInputsModel", class(InputsModel)) # Update griwrm in case of manual change in model column griwrm$donor <- sapply(griwrm$id, getGaugedId, griwrm = griwrm) attr(InputsModel, "GRiwrm") <- griwrm return(InputsModel) } #' Create one InputsModel for a **airGRiwrm** node #' #' @param id string of the node identifier #' @param griwrm See [CreateGRiwrm]) #' @param ... parameters sent to [airGR::CreateInputsModel]: #' - `DatesR` [vector] of dates required to create the GR model and CemaNeige module inputs #' - `Precip` [vector] time series of potential evapotranspiration (catchment average) (mm/time step) #' - `PotEvap` [vector] time series of potential evapotranspiration (catchment average) (mm/time step) #' @param Qobs Matrix or data frame of numeric containing observed flow (mm/time step). Column names correspond to node IDs #' #' @return \emph{InputsModel} object for one. #' @noRd CreateOneGRiwrmInputsModel <- function(id, griwrm, ..., Qobs, Qmin) { hasDiversion <- "Diversion" %in% getNodeProperties(id, griwrm) if (hasDiversion) { rowDiv <- which(griwrm$id == id & griwrm$model == "Diversion") hasDiversion <- TRUE diversionOutlet <- griwrm$down[rowDiv] griwrm <- griwrm[-rowDiv, ] } node <- griwrm[griwrm$id == id,] FUN_MOD <- griwrm$model[griwrm$id == griwrm$donor[griwrm$id == id]] # Set hydraulic parameters UpstreamNodeRows <- which(griwrm$down == id & !is.na(griwrm$down)) Qupstream <- NULL LengthHydro <- NULL BasinAreas <- NULL if(length(UpstreamNodeRows) > 0) { # Sub-basin with hydraulic routing Qupstream <- as.matrix(Qobs[ , griwrm$id[UpstreamNodeRows], drop=FALSE]) LengthHydro <- griwrm$length[UpstreamNodeRows] names(LengthHydro) <- griwrm$id[UpstreamNodeRows] BasinAreas <- c( griwrm$area[UpstreamNodeRows], node$area - sum(griwrm$area[UpstreamNodeRows], na.rm = TRUE) ) if (BasinAreas[length(BasinAreas)] < 0) { stop(sprintf( "Area of the catchment %s must be greater than the sum of the areas of its upstream catchments", id )) } names(BasinAreas) <- c(griwrm$id[UpstreamNodeRows], id) }
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
# Set model inputs with the **airGR** function InputsModel <- CreateInputsModel( FUN_MOD, ..., Qupstream = Qupstream, LengthHydro = LengthHydro, BasinAreas = BasinAreas ) # Add Identifiers of connected nodes in order to be able to update them with simulated flows InputsModel$id <- id InputsModel$down <- node$down if(length(UpstreamNodeRows) > 0) { InputsModel$UpstreamNodes <- griwrm$id[UpstreamNodeRows] InputsModel$UpstreamIsModeled <- !is.na(griwrm$model[UpstreamNodeRows]) InputsModel$UpstreamVarQ <- ifelse(!is.na(griwrm$model[UpstreamNodeRows]) & griwrm$model[UpstreamNodeRows] == "Diversion", "Qdiv_m3", "Qsim_m3") } else { InputsModel$BasinAreas <- node$area } # Add the model function InputsModel$FUN_MOD <- FUN_MOD featModel <- .GetFeatModel(InputsModel) InputsModel$isUngauged <- griwrm$model[griwrm$id == id] == "Ungauged" InputsModel$gaugedId <- griwrm$donor[griwrm$id == id] InputsModel$hasUngaugedNodes <- hasUngaugedNodes(id, griwrm) InputsModel$model <- list(indexParamUngauged = ifelse(inherits(InputsModel, "SD"), 0, 1) + seq.int(featModel$NbParam), hasX4 = grepl("RunModel_GR[456][HJ]", FUN_MOD), iX4 = ifelse(inherits(InputsModel, "SD"), 5, 4)) InputsModel$hasDiversion <- hasDiversion if (hasDiversion) { InputsModel$diversionOutlet <- diversionOutlet InputsModel$Qdiv <- -Qobs[, id] InputsModel$Qmin <- Qmin } return(InputsModel) } #' Check of time steps of the model for all nodes and return of the time step in seconds #' #' Function that is called inside [CreateInputsModel.GRiwrm] for defining the time step of the complete model #' #' @param InputsModel \[object of class `GRiwrmInputsModel`\] #' #' @return [numeric] time step in seconds #' @noRd getModelTimeStep <- function(InputsModel) { TS <- sapply(InputsModel, function(x) { if (inherits(x, "hourly")) { TimeStep <- 60 * 60 } else if (inherits(x, "daily")) { TimeStep <- 60 * 60 * 24 } else { stop("All models should be at hourly or daily time step") } }) if(length(unique(TS)) != 1) { stop("Time steps of the model of all nodes should be identical") } return(unique(TS)) } #' Select the node input for input arguments of [airGR::CreateInputsModel] #' #' @param x [matrix] [data.frame] or named [vector] the input argument
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
#' @param id [character] the id of the node #' @param unset default value if the id is not found in `x` #' #' @return the selected column or value in respect to `id` #' @noRd getInputBV <- function(x, id, unset = NULL) { if(is.null(x)) { return(unset) } if (is.matrix(x) || is.data.frame(x)) { if (!id %in% colnames(x)) { return(unset) } } else { # vector (for ZInputs and NLayers) if (length(x) == 1 && is.null(names(x))) { return(x) } else if(!id %in% names(x)) { return(unset) } else { return(x[id]) } } return(x[, id]) } #' Check if current node contains ungauged nodes that shares its parameters #' #' @param id id [character] Id of the current node #' @param griwrm See [CreateGRiwrm]) #' #' @return A [logical], `TRUE` if the node `id` contains ungauged nodes. #' #' @noRd hasUngaugedNodes <- function(id, griwrm) { upIds <- griwrm$id[griwrm$down == id] upIds <- upIds[!is.na(upIds)] # No upstream nodes if(length(upIds) == 0) return(FALSE) # At least one upstream node is ungauged UngNodes <- griwrm$model[griwrm$id %in% upIds] == "Ungauged" UngNodes <- UngNodes[!is.na(UngNodes)] if(length(UngNodes) > 0 && any(UngNodes)) return(TRUE) # At least one node's model is NA need to investigate next level if(any(is.na(griwrm$model[griwrm$id %in% upIds]))) { g <- griwrm[griwrm$id %in% upIds, ] NaIds <- g$id[is.na(g$model)] out <- sapply(NaIds, hasUngaugedNodes, griwrm = griwrm) return(any(out)) } return(FALSE) } #' function to extract model features partially copied from airGR:::.GetFeatModel #' @importFrom utils tail #' @noRd .GetFeatModel <- function(InputsModel) { path <- system.file("modelsFeatures/FeatModelsGR.csv", package = "airGR") FeatMod <- read.table(path, header = TRUE, sep = ";", stringsAsFactors = FALSE) NameFunMod <- ifelse(test = FeatMod$Pkg %in% "airGR", yes = paste("RunModel", FeatMod$NameMod, sep = "_"), no = FeatMod$NameMod) IdMod <- which(sapply(NameFunMod, FUN = function(x) identical(InputsModel$FUN_MOD, x))) if (length(IdMod) < 1) { stop("'FUN_MOD' must be one of ", paste(NameFunMod, collapse = ", ")) } FeatMod <- as.list(FeatMod[IdMod, ]) FeatMod$IsSD <- inherits(InputsModel, "SD")
421422423424425426
if (FeatMod$IsSD) { FeatMod$NbParam <- FeatMod$NbParam + 1 } return(FeatMod) }