CreateInputsModel.GRiwrm.R 19.62 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 Qrelease (optional) [matrix] or [data.frame] of [numeric] containing
#'        release flows by nodes using the model `RunModel_Reservoir` \[m3 per
#'        time step\]
#' @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 IsHyst [logical] boolean indicating if the hysteresis version of
#'        CemaNeige is used. See details of [airGR::CreateRunOptions].
#' @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,
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
Precip = NULL, PotEvap = NULL, Qobs = NULL, Qmin = NULL, Qrelease = NULL, PrecipScale = TRUE, TempMean = NULL, TempMin = NULL, TempMax = NULL, ZInputs = NULL, HypsoData = NULL, NLayers = 5, IsHyst = FALSE, ...) { # 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)) } } }) if (is.null(Qobs)) Qobs <- matrix(0, ncol = 0, nrow = length(DatesR)) if (is.null(Qrelease)) Qrelease <- matrix(0, ncol = 0, nrow = length(DatesR)) l <- updateQObsQrelease(g = x, Qobs = Qobs, Qrelease = Qrelease) Qobs <- l$Qobs Qrelease <- l$Qrelease checkQobsQrelease(x, "Qobs", Qobs) checkQobsQrelease(x, "Qrelease", Qrelease) diversionRows <- getDiversionRows(x) if (length(diversionRows) > 0) { warn <- FALSE if (is.null(Qmin)) { warn <- TRUE } else {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
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(x$id[diversionRows], 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) 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, Qmin = getInputBV(Qmin, id), Qrelease = Qrelease, IsHyst = IsHyst ) } 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
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
CreateEmptyGRiwrmInputsModel <- function(griwrm) { InputsModel <- list() class(InputsModel) <- c("GRiwrmInputsModel", class(InputsModel)) # Save the GRiwrm in InputsModel for later use 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, DatesR, ..., Qobs, Qmin, Qrelease, IsHyst) { np <- getNodeProperties(id, griwrm) if (np$Diversion) { rowDiv <- which(griwrm$id == id & griwrm$model == "Diversion") diversionOutlet <- griwrm$down[rowDiv] griwrm <- griwrm[-rowDiv, ] } node <- griwrm[griwrm$id == id,] g2 <- griwrm[getDiversionRows(griwrm, TRUE), ] FUN_MOD <- g2$model[g2$id == g2$donor[g2$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 <- NULL Qupstream <- as.matrix(cbind( Qobs[ , colnames(Qobs)[colnames(Qobs) %in% griwrm$id[UpstreamNodeRows]], drop = FALSE], Qrelease[ , colnames(Qrelease)[colnames(Qrelease) %in% griwrm$id[UpstreamNodeRows]], drop = FALSE] )) # Qupstream completion with zeros for all upstream nodes Qupstream0 <- matrix(0, nrow = length(DatesR), ncol = length(UpstreamNodeRows)) colnames(Qupstream0) <- griwrm$id[UpstreamNodeRows] if (is.null(Qupstream) || ncol(Qupstream) == 0) { Qupstream <- Qupstream0 } else { Qupstream0[, colnames(Qupstream)] <- Qupstream Qupstream <- Qupstream0 } upstreamDiversion <- which( sapply(griwrm$id[UpstreamNodeRows], function(id) { getNodeProperties(id, griwrm)$Diversion }) ) if (length(upstreamDiversion) > 0) { Qupstream[, upstreamDiversion] <- - Qupstream[, upstreamDiversion] } LengthHydro <- griwrm$length[UpstreamNodeRows] names(LengthHydro) <- griwrm$id[UpstreamNodeRows] upstreamAreas <- sapply(UpstreamNodeRows, getNodeBasinArea, griwrm = griwrm) BasinAreas <- c( upstreamAreas,
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
node$area - sum(upstreamAreas, na.rm = TRUE) ) if (!is.na(node$area)) { 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) } FUN_MOD_REAL <- FUN_MOD if (np$Reservoir) { FUN_MOD <- "RunModel_Lag" FUN_MOD_REAL <- "RunModel_Reservoir" } # Set model inputs with the **airGR** function InputsModel <- CreateInputsModel( FUN_MOD, DatesR = DatesR, ..., 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]) names(InputsModel$UpstreamIsModeled) <- InputsModel$UpstreamNodes InputsModel$UpstreamVarQ <- ifelse(!is.na(griwrm$model[UpstreamNodeRows]) & griwrm$model[UpstreamNodeRows] == "Diversion", "Qdiv_m3", "Qsim_m3") names(InputsModel$UpstreamVarQ) <- InputsModel$UpstreamNodes } else { InputsModel$BasinAreas <- node$area } # Add the model function InputsModel$FUN_MOD <- FUN_MOD featModel <- .GetFeatModel(InputsModel, IsHyst) 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_(CemaNeige|)GR[456][HJ]", FUN_MOD), iX4 = ifelse(inherits(InputsModel, "SD"), 5, 4), IsHyst = featModel$IsHyst ) InputsModel$hasDiversion <- np$Diversion InputsModel$isReservoir <- np$Reservoir # Add specific properties for Diversion and Reservoir nodes if (np$Diversion) { InputsModel$diversionOutlet <- diversionOutlet InputsModel$Qdiv <- -Qobs[, id] InputsModel$Qmin <- Qmin } else if(np$Reservoir) { # If an upstream node is ungauged and the donor is downstream then we are in an ungauged reduced network iUpstreamUngaugedNodes <- which(griwrm$id %in% griwrm$id[UpstreamNodeRows] & griwrm$model == "Ungauged")
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
# Fall back to non diversion node in order to get correct donor if (length(iUpstreamUngaugedNodes) > 0) { InputsModel$isUngauged <- any(griwrm$donor[iUpstreamUngaugedNodes] == InputsModel$gaugedId) } # Fill reservoir release with Qobs InputsModel$Qrelease <- Qrelease[, id] } # Add class for S3 process (Prequel of HYCAR-Hydro/airgr#60) class(InputsModel) <- c(FUN_MOD_REAL, class(InputsModel)) 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, "GR")) { if (inherits(x, "hourly")) { return(60 * 60) } else if (inherits(x, "daily")) { return(60 * 60 * 24) } else { stop("All models should be at hourly or daily time step") } } return(NA) }) TS <- TS[!is.na(TS)] 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 #' @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])
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
} #' 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) { nps <- getAllNodesProperties(griwrm) upNodes <- griwrm[!is.na(griwrm$down) & griwrm$down == id, ] upIds <- upNodes$id[upNodes$model != "Diversion"] # 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) upNps <- nps[nps$id %in% upIds, ] if(any(upNps$DirectInjection) || any(upNps$Reservoir)) { # At least one node's model is NA or Reservoir, we need to investigate next level out <- sapply(upNps$id[upNps$DirectInjection | upNps$Reservoir], 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, IsHyst) { 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") if (FeatMod$IsSD) { FeatMod$NbParam <- FeatMod$NbParam + 1 } FeatMod$IsHyst <- FALSE if (grepl("CemaNeige", FeatMod$NameMod)) { FeatMod$IsHyst <- IsHyst if (IsHyst) { FeatMod$NbParam <- FeatMod$NbParam + 2 } } return(FeatMod) } #' Return the basin area of a node #' #' If the current node doesn't have an area (for example, for a Reservoir, #' a Direct Injection, or a [airGR::RunModel_Lag]), this function return the sum #' of the basin areas of the upstream nodes. #' #' @param i [integer] row number in the GRiwrm network
491492493494495496497498499500501502503504505506507508509510511
#' @param griwrm A GRiwrm #' #' @return The basin area of the node #' @noRd #' getNodeBasinArea <- function(i, griwrm) { area <- griwrm$area[i] if (!is.na(area)) return(area) Diversions <- !is.na(griwrm$model) & griwrm$model == "Diversion" if (i %in% which(Diversions)) return(NA) UpstreamNodeRows <- which(griwrm$down == griwrm$id[i] & !is.na(griwrm$down) & !Diversions) if(length(UpstreamNodeRows) > 0) { upstreamAreas <- sapply(UpstreamNodeRows, getNodeBasinArea, griwrm = griwrm) return(sum(upstreamAreas, na.rm = TRUE)) } else { return(NA) } }