diff --git a/R/CreateSupervisor.R b/R/CreateSupervisor.R index 660f4ecba09c9e98a2ebf4511d81e52822a90ab0..07ab00bb7e0aeb07c75a5853887f0badf29dd793 100644 --- a/R/CreateSupervisor.R +++ b/R/CreateSupervisor.R @@ -24,6 +24,7 @@ #' InputsModel <- CreateInputsModel(griwrm, DatesR, Precip, PotEvap, Qobs) #' sv <- CreateSupervisor(InputsModel) CreateSupervisor <- function(InputsModel) { + if(!inherits(InputsModel, "GRiwrmInputsModel")) stop("`InputsModel` parameter must of class 'GRiwrmInputsModel' (See ?CreateInputsModel.GRiwrm)") # Create Supervisor environment in the parent of GlobalEnv e <- new.env(parent = parent.env(globalenv())) class(e) <- c("Supervisor", class(e)) diff --git a/R/RunModel.GR.R b/R/RunModel.GR.R index e4fa5562d1a4cfc884e85b2f5f0728e5d3d73470..da2bc072a62b7dc6f720c4df406734898db192fa 100644 --- a/R/RunModel.GR.R +++ b/R/RunModel.GR.R @@ -7,7 +7,6 @@ #' @export #' RunModel.GR <- function(x, RunOptions, Param, ...) { - message("RunModel.GR") if (inherits(x, "SD")) { # Lag model take one parameter at the beginning of the vector diff --git a/R/RunModel.GRiwrmInputsModel.R b/R/RunModel.GRiwrmInputsModel.R index a3d894d28db021f1155a5c07225fbc2cd215b06a..ff6047f1733a38f1a2c35afcf3a5444e9b87d29d 100644 --- a/R/RunModel.GRiwrmInputsModel.R +++ b/R/RunModel.GRiwrmInputsModel.R @@ -8,32 +8,25 @@ #' @return \emph{GRiwrmOutputsModel} object which is a list of \emph{OutputsModel} objects (See \code{\link[airGR]{RunModel}}) for each node of the semi-distributed model. #' @export RunModel.GRiwrmInputsModel <- function(x, RunOptions, Param, ...) { - message("RunModel.GRiwrmInputsModel") - # Run runoff model for each sub-basin - OutputsModel <- lapply(X = x, FUN = function(IM) { - RunModel.GR(IM, - RunOptions = RunOptions[[IM$id]], - Param = Param[[IM$id]]) - }) - class(OutputsModel) <- append(class(OutputsModel), "GRiwrmOutputsModel") + checkRunModelParameters(x, RunOptions, Param) - # Loop over sub-basin using SD model - for(id in getSD_Ids(x)) { - IM <- x[[id]] - message("RunModel.GRiwrmInputsModel: Treating sub-basin ", id, "...") + OutputsModel <- list() + class(OutputsModel) <- c("GRiwrmOutputsModel", class(OutputsModel)) + + for(IM in x) { + message("RunModel.GRiwrmInputsModel: Treating sub-basin ", IM$id, "...") # Update x$Qupstream with simulated upstream flows if(any(IM$UpstreamIsRunoff)) { - IM <- UpdateQsimUpstream(IM, RunOptions[[id]]$IndPeriod_Run, OutputsModel) + IM <- UpdateQsimUpstream(IM, RunOptions[[IM$id]]$IndPeriod_Run, OutputsModel) } - # Run the SD model for the sub-basin - OutputsModel[[id]] <- RunModel.SD( + # Run the model for the sub-basin + OutputsModel[[IM$id]] <- RunModel.InputsModel( IM, - RunOptions = RunOptions[[id]], - Param = Param[[id]], - OutputsModel[[id]] + RunOptions = RunOptions[[IM$id]], + Param = Param[[IM$id]] ) } diff --git a/R/RunModel.R b/R/RunModel.R index a3ea4600cbb87eb55c6b3d3e05dcd367797e6a0b..0ac7f23a9c50d35ca70a0877a1f8f1d3693f3727 100644 --- a/R/RunModel.R +++ b/R/RunModel.R @@ -6,6 +6,5 @@ #' @return Either a [list] of OutputsModel object (for GRiwrmInputsModel) or an OutputsModel object (for InputsModel) #' @export RunModel <- function(x, ...) { - message("RunModel") UseMethod("RunModel", x) } diff --git a/R/RunModel.SD.R b/R/RunModel.SD.R index 95f661f1b32aa1882b299d9d4f08ddbe55c14292..dfff38feefe01349a170c8c2ca367058ae2bb382 100644 --- a/R/RunModel.SD.R +++ b/R/RunModel.SD.R @@ -1,15 +1,14 @@ #' Run SD Model from run-off model outputs #' #' @inheritParams airGR::RunModel_Lag -#' @param x `InputsModel` used as `InputsModel` parameter for [airGR::RunModel] -#' @param OutputsModel `OutputsModel` object returned by a GR model by [airGR::RunModel] +#' @param x `InputsModel` used as `InputsModel` parameter for [airGR::RunModel_Lag] +#' @param QSimDown a [numeric] corresponding to the runoff of the sub-basin (Typically the `Qsim` outputs of the GR model) #' @param ... further arguments passed to or from other methods #' #' @return `OutputsModel` object. See [airGR::RunModel_Lag] #' @export #' -RunModel.SD <- function(x, RunOptions, Param, OutputsModel, ...) { - message("RunModel.SD") - x$OutputsModel <- OutputsModel +RunModel.SD <- function(x, RunOptions, Param, QsimDown, ...) { + x$OutputsModel <- list(Qsim = QsimDown) RunModel_Lag(x, RunOptions = RunOptions, Param = Param[1]) } diff --git a/R/RunModel.Supervisor.R b/R/RunModel.Supervisor.R index f847ecca30a3cf0019d11973b6d98f00851e1603..9f35149d53bc13077d5731405d97d0a61fa2e195 100644 --- a/R/RunModel.Supervisor.R +++ b/R/RunModel.Supervisor.R @@ -14,32 +14,53 @@ RunModel.Supervisor <- function(x, RunOptions, Param, ...) { }) # Run runoff model for each sub-basin - OutputsModel <- lapply(X = x$InputsModel, FUN = function(IM) { + x$OutputsModel <- lapply(X = x$InputsModel, FUN = function(IM) { RunModel.GR(IM, RunOptions = RunOptions[[IM$id]], Param = Param[[IM$id]]) }) - class(OutputsModel) <- append(class(OutputsModel), "GRiwrmOutputsModel") + class(x$OutputsModel) <- append(class(x$OutputsModel), "GRiwrmOutputsModel") + # Save Qsim for step by step simulation + Qsim <- lapply(x$OutputsModel, function(OM) { + OM$Qsim + }) - # Loop over time steps - # Loop over sub-basin using SD model + # Adapt RunOptions to step by step simulation for(id in getSD_Ids(x$InputsModel)) { - IM <- x$InputsModel[[id]] - message("RunModel.GRiwrmInputsModel: Treating sub-basin ", id, "...") + RunOptions[[id]]$IndPeriod_WarmUp <- 0L + RunOptions[[id]]$Outputs_Sim <- "StateEnd" + } - # Update InputsModel$Qupstream with simulated upstream flows - if(any(IM$UpstreamIsRunoff)) { - IM <- UpdateQsimUpstream(IM, RunOptions[[id]]$IndPeriod_Run, OutputsModel) - } + # Loop over time steps + for(iTS in RunOptions[[1]]$IndPeriod_Run) { + # Run regulation on the whole basin for the current time step + x$ts.index <- iTS + x$ts.date <- x$InputsModel[[1]]$DatesR[iTS] + doSupervision(x) - # Run the SD model for the sub-basin - OutputsModel[[id]] <- RunModel.SD( - IM, - RunOptions = RunOptions[[id]], - Param = Param[[id]], - OutputsModel[[id]] - ) + # Loop over sub-basin using SD model + for(id in getSD_Ids(x$InputsModel)) { + # Update InputsModel$Qupstream with simulated upstream flows + for(i in which(x$InputsModel[[id]]$UpstreamIsRunoff)) { + x$InputsModel[[id]]$Qupstream[iTS, i] <- + x$OutputsModel[[x$InputsModel[[id]]$UpstreamNodes[i]]]$Qsim[iTS - x$ts.index0] + } + + # Run the SD model for the sub-basin and one time step + RunOptions[[id]]$IndPeriod_Run <- iTS + RunOptions[[id]]$IniStates <- unlist(x$OutputsModel[[id]]$StateEnd) + x$OutputsModel[[id]] <- RunModel.SD( + x$InputsModel[[id]], + RunOptions = RunOptions[[id]], + Param = Param[[id]], + QsimDown = Qsim[[id]][iTS - x$ts.index0] + ) + Qsim[[id]][iTS - x$ts.index0] <- x$OutputsModel[[id]]$Qsim + } + } + for(id in getSD_Ids(x$InputsModel)) { + x$OutputsModel[[id]]$Qsim <- Qsim[[id]] } - return(OutputsModel) + return(x$OutputsModel) } diff --git a/R/RunModel_Lag.R b/R/RunModel_Lag.R new file mode 100644 index 0000000000000000000000000000000000000000..e3bb075b1fa903c07c3df5002c6d35fce018ab52 --- /dev/null +++ b/R/RunModel_Lag.R @@ -0,0 +1,108 @@ +RunModel_Lag <- function(InputsModel, RunOptions, Param) { + NParam <- 1 + + ##Arguments_check + if (!inherits(InputsModel, "InputsModel")) { + stop("'InputsModel' must be of class 'InputsModel'") + } + if (!inherits(InputsModel, "SD")) { + stop("'InputsModel' must be of class 'SD'") + } + if (!inherits(RunOptions, "RunOptions")) { + stop("'RunOptions' must be of class 'RunOptions'") + } + if (!is.vector(Param) | !is.numeric(Param)) { + stop("'Param' must be a numeric vector") + } + if (sum(!is.na(Param)) != NParam) { + stop(paste("'Param' must be a vector of length", NParam, "and contain no NA")) + } + if (is.null(InputsModel$OutputsModel)) { + stop( + "'InputsModel' should contain an 'OutputsModel' key containing the output of the runoff of the downstream subcatchment" + ) + } + if (is.null(InputsModel$OutputsModel$Qsim)) { + stop( + "'InputsModel$OutputsModel' should contain a key 'Qsim' containing the output of the runoff of the downstream subcatchment" + ) + } + if (sum(!is.na(InputsModel$OutputsModel$Qsim)) != length(RunOptions$IndPeriod_Run)) { + stop( + "'InputsModel$OutputsModel$Qim' should have the same lenght as 'RunOptions$IndPeriod_Run' and contain no NA" + ) + } + + OutputsModel <- InputsModel$OutputsModel + OutputsModel$QsimDown <- OutputsModel$Qsim + + if (inherits(InputsModel, "hourly")) { + TimeStep <- 60 * 60 + } else if (inherits(InputsModel, "daily")) { + TimeStep <- 60 * 60 * 24 + } else { + stop("'InputsModel' should be of class \"daily\" or \"hourly\"") + } + + # propagation time from upstream meshes to outlet + PT <- InputsModel$LengthHydro / Param[1L] / TimeStep + HUTRANS <- rbind(1 - (PT - floor(PT)), PT - floor(PT)) + + NbUpBasins <- length(InputsModel$LengthHydro) + LengthTs <- length(OutputsModel$QsimDown) + OutputsModel$Qsim <- OutputsModel$QsimDown * InputsModel$BasinAreas[length(InputsModel$BasinAreas)] * 1e3 + + IniSD <- RunOptions$IniStates[grep("SD", names(RunOptions$IniStates))] + if (length(IniSD) > 0) { + if (sum(floor(PT)) + NbUpBasins != length(IniSD)) { + stop( + sprintf( + "SD initial states has a length of %i and a length of %i is required", + length(IniSD), + sum(floor(PT)) + NbUpBasins + ) + ) + } + IniStates <- lapply(seq_len(NbUpBasins), function(x) { + iStart <- 1 + if (x > 1) { + iStart <- iStart + sum(floor(PT[1:x - 1]) + 1) + } + IniSD[iStart:(iStart + PT[x])] + }) + } else { + IniStates <- lapply(seq_len(NbUpBasins), function(x) { + rep(0, floor(PT[x] + 1)) + }) + } + + for (upstream_basin in seq_len(NbUpBasins)) { + Qupstream <- InputsModel$Qupstream[RunOptions$IndPeriod_Run, upstream_basin] + if (!is.na(InputsModel$BasinAreas[upstream_basin])) { + # Upstream flow with area needs to be converted to m3 by time step + Qupstream <- Qupstream * InputsModel$BasinAreas[upstream_basin] * 1e3 + } + Qupstream1 <- c(IniStates[[upstream_basin]][-length(IniStates[[upstream_basin]])], Qupstream[1:(LengthTs - floor(PT[upstream_basin]))]) + Qupstream2 <- IniStates[[upstream_basin]] + if(LengthTs - floor(PT[upstream_basin]) - 1 > 0) Qupstream2 <- c(Qupstream2, Qupstream[1:(LengthTs - floor(PT[upstream_basin]) - 1)]) + OutputsModel$Qsim <- OutputsModel$Qsim + + Qupstream1 * HUTRANS[1, upstream_basin] + + Qupstream2 * HUTRANS[2, upstream_basin] + } + # Warning for negative flows + if (any(OutputsModel$Qsim < 0)) { + warning(length(which(OutputsModel$Qsim < 0)), " time steps with negative flow, set to zero.") + OutputsModel$Qsim[OutputsModel$Qsim < 0] <- 0 + } + # Convert back Qsim to mm + OutputsModel$Qsim <- OutputsModel$Qsim / sum(InputsModel$BasinAreas, na.rm = TRUE) / 1e3 + + if ("StateEnd" %in% RunOptions$Outputs_Sim) { + OutputsModel$StateEnd$SD <- lapply(seq(NbUpBasins), function(x) { + LengthTs <- tail(RunOptions$IndPeriod_Run,1) + InputsModel$Qupstream[(LengthTs - floor(PT[x])):LengthTs, x] + }) + } + + return(OutputsModel) +} diff --git a/R/utils.R b/R/utils.R index f2fac0a8ad679df4c64acaa2f24475da21786d35..4cf16933935889bd01ffdb97c01e305beb789d47 100644 --- a/R/utils.R +++ b/R/utils.R @@ -53,7 +53,6 @@ setDataToLocation <- function(control, supervisor) { #' @return [NULL] doSupervision <- function(supervisor) { for (id in names(supervisor$controllers)) { - ctrlr <- supervisor$controllers[[id]] # Read Y from locations in the model supervisor$controllers[[id]]$Y$v <- sapply(supervisor$controllers[[id]]$Y$loc, getDataFromLocation, supervisor = supervisor) @@ -65,3 +64,19 @@ doSupervision <- function(supervisor) { } return() } + +#' Check the parameters of RunModel methods +#' +#' Stop the execution if an error is detected. +#' +#' @param RunOptions a `GRiwrmRunOptions` object (See [CreateRunOptions.GRiwrmInputsModel]) +#' @param Param a [list] of [numeric] containing model parameters of each node of the network +#' +#' @return [NULL] +#' +checkRunModelParameters <- function(InputsModel, RunOptions, Param) { + if(!inherits(InputsModel, "GRiwrmInputsModel")) stop("`InputsModel` parameter must of class 'GRiwrmRunoptions' (See ?CreateRunOptions.GRiwrmInputsModel)") + if(!inherits(RunOptions, "GRiwrmRunOptions")) stop("Argument `RunOptions` parameter must of class 'GRiwrmRunOptions' (See ?CreateRunOptions.GRiwrmInputsModel)") + if(!is.list(Param) || !all(names(InputsModel) %in% names(Param))) stop("Argument `Param` must be a list with names equal to nodes IDs") + return() +} diff --git a/tests/testthat/test-RunModel.R b/tests/testthat/test-RunModel.R new file mode 100644 index 0000000000000000000000000000000000000000..b12867f026b01acf99a3508f4b77751c613348a1 --- /dev/null +++ b/tests/testthat/test-RunModel.R @@ -0,0 +1,43 @@ +context("RunModel.Supervisor") + +test_that("RunModelSupervisor with no regulation should returns same results as RunModel.GRiwrmInputsModel", { + data(Severn) + # Network configuration + nodes <- Severn$BasinsInfo[c(1,2,5), c("gauge_id", "downstream_id", "distance_downstream", "area")] + nodes$distance_downstream <- nodes$distance_downstream * 1000 # Conversion km -> m + nodes$model <- NA + nodes$model[1] <- "RunModel_GR4J" + griwrm <- GRiwrm(nodes, list(id = "gauge_id", down = "downstream_id", length = "distance_downstream")) + # InputsModel + DatesR <- Severn$BasinsObs[[1]]$DatesR + PrecipTot <- cbind(sapply(Severn$BasinsObs, function(x) {x$precipitation})) + PotEvapTot <- cbind(sapply(Severn$BasinsObs, function(x) {x$peti})) + Precip <- ConvertMeteoSD(griwrm, PrecipTot) + PotEvap <- ConvertMeteoSD(griwrm, PotEvapTot) + Qobs <- cbind(sapply(Severn$BasinsObs, function(x) {x$discharge_spec})) + InputsModel <- CreateInputsModel(griwrm, DatesR, Precip, PotEvap, Qobs) + # RunOptions + IndPeriod_Run <- seq( + length(InputsModel[[1]]$DatesR) - 365, + length(InputsModel[[1]]$DatesR) + ) + IndPeriod_WarmUp = seq(IndPeriod_Run[1]-366,IndPeriod_Run[1]-1) + RunOptions <- CreateRunOptions( + InputsModel = InputsModel, + IndPeriod_WarmUp = IndPeriod_WarmUp, + IndPeriod_Run = IndPeriod_Run + ) + Param <- list("54057" = c(0.727, 175.493, -0.082, 0.029, 4.654)) + OM_GriwrmInputs <- RunModel( + InputsModel, + RunOptions = RunOptions, + Param = Param + ) + supervisor <- CreateSupervisor(InputsModel) + OM_Supervisor <- RunModel( + supervisor, + RunOptions = RunOptions, + Param = Param + ) + expect_equal(OM_Supervisor[["54057"]]$Qsim, OM_GriwrmInputs[["54057"]]$Qsim) +})