diff --git a/R/CreateInputsModel.GRiwrm.R b/R/CreateInputsModel.GRiwrm.R index 2d8ad3bacab51ea6d3186409929bc39a01b2166d..04ab3f8dab0185524b8849d5e8c4b92e8dc9f990 100644 --- a/R/CreateInputsModel.GRiwrm.R +++ b/R/CreateInputsModel.GRiwrm.R @@ -11,8 +11,8 @@ #' @export CreateInputsModel.GRiwrm <- function(x, DatesR, Precip, PotEvap, Qobs, ...) { - InputsModel <- CreateEmptyGRiwrmInputsModel() - Qobs[is.na(Qobs)] <- -99 # airGRCreateInputsModel doesn't accept NA values + InputsModel <- CreateEmptyGRiwrmInputsModel(x) + Qobs[is.na(Qobs)] <- -99 # airGR::CreateInputsModel doesn't accept NA values for(id in getNodeRanking(x)) { message("CreateInputsModel.GRiwrm: Treating sub-basin ", id, "...") @@ -27,9 +27,10 @@ CreateInputsModel.GRiwrm <- function(x, DatesR, Precip, PotEvap, Qobs, ...) { #' Create an empty InputsModel object for **airGRiwrm** nodes #' #' @return \emph{GRiwrmInputsModel} empty object -CreateEmptyGRiwrmInputsModel <- function() { +CreateEmptyGRiwrmInputsModel <- function(griwrm) { InputsModel <- list() class(InputsModel) <- c("GRiwrmInputsModel", class(InputsModel)) + attr(InputsModel, "GRiwrm") <- griwrm return(InputsModel) } diff --git a/R/CreateSupervisor.R b/R/CreateSupervisor.R index 07ab00bb7e0aeb07c75a5853887f0badf29dd793..0d698a50d13423efb58cb38658b48120c8bd3d5c 100644 --- a/R/CreateSupervisor.R +++ b/R/CreateSupervisor.R @@ -25,7 +25,7 @@ #' 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 + # Create Supervisor environment from the parent of GlobalEnv e <- new.env(parent = parent.env(globalenv())) class(e) <- c("Supervisor", class(e)) @@ -35,8 +35,9 @@ CreateSupervisor <- function(InputsModel) { # Add pointer to itself in order to assign variable from function environment e$supervisor <- e - # Copy of the InputsModel + # Copy of InputsModel, griwrm and prepare OutputsModel e$InputsModel <- InputsModel + e$griwrm <- attr(InputsModel, "GRiwrm") e$OutputsModel <- list() # Controller list diff --git a/R/RunModel.Supervisor.R b/R/RunModel.Supervisor.R index 9f35149d53bc13077d5731405d97d0a61fa2e195..6a10e2bec83f977eca69adeb7ee868964540727b 100644 --- a/R/RunModel.Supervisor.R +++ b/R/RunModel.Supervisor.R @@ -9,9 +9,7 @@ #' @export RunModel.Supervisor <- function(x, RunOptions, Param, ...) { - x$ts.index0 <- sapply(RunOptions, function(x) { - x$IndPeriod_Run[1] - 1 - }) + x$ts.index0 <- RunOptions[[1]]$IndPeriod_Run[1] - 1 # Run runoff model for each sub-basin x$OutputsModel <- lapply(X = x$InputsModel, FUN = function(IM) { @@ -20,6 +18,13 @@ RunModel.Supervisor <- function(x, RunOptions, Param, ...) { Param = Param[[IM$id]]) }) class(x$OutputsModel) <- append(class(x$OutputsModel), "GRiwrmOutputsModel") + # Copy simulated pure runoff flows (no SD nodes) to Qupstream in downstream SD nodes + for(id in getNoSD_Ids(x$InputsModel)) { + downId <- x$InputsModel[[id]]$down + x$InputsModel[[downId]]$Qupstream[RunOptions[[downId]]$IndPeriod_Run, id] <- + x$OutputsModel[[id]]$Qsim + } + # Save Qsim for step by step simulation Qsim <- lapply(x$OutputsModel, function(OM) { OM$Qsim @@ -34,19 +39,17 @@ RunModel.Supervisor <- function(x, RunOptions, Param, ...) { # 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.index <- iTS - x$ts.index0 x$ts.date <- x$InputsModel[[1]]$DatesR[iTS] - doSupervision(x) + # Regulation occurs from second time step + if(iTS > RunOptions[[1]]$IndPeriod_Run[1]) { + doSupervision(x) + message("Supervision done") + } # 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) @@ -54,9 +57,15 @@ RunModel.Supervisor <- function(x, RunOptions, Param, ...) { x$InputsModel[[id]], RunOptions = RunOptions[[id]], Param = Param[[id]], - QsimDown = Qsim[[id]][iTS - x$ts.index0] + QsimDown = Qsim[[id]][x$ts.index] ) - Qsim[[id]][iTS - x$ts.index0] <- x$OutputsModel[[id]]$Qsim + # Storing Qsim in the data.frame Qsim + Qsim[[id]][x$ts.index] <- x$OutputsModel[[id]]$Qsim + # Routing Qsim to the downstream node + if(!is.na(x$InputsModel[[id]]$down)) { + x$InputsModel[[x$InputsModel[[id]]$down]]$Qupstream[iTS, i] <- + x$OutputsModel[[id]]$Qsim + } } } for(id in getSD_Ids(x$InputsModel)) { diff --git a/R/RunModel_Lag.R b/R/RunModel_Lag.R index b625ac1f6b98a679d714a4224ed580ce44a3a9aa..398478a045c1fa0634ac6a4fcb1e2a0a046105a5 100644 --- a/R/RunModel_Lag.R +++ b/R/RunModel_Lag.R @@ -78,20 +78,17 @@ RunModel_Lag <- function(InputsModel, RunOptions, Param) { #message("Initstates: ",paste(IniStates, collapse = ", ")) for (upstream_basin in seq_len(NbUpBasins)) { - Qupstream <- InputsModel$Qupstream[RunOptions$IndPeriod_Run, upstream_basin] + Qupstream <- c(IniStates[[upstream_basin]], + 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)]) - #message("Qupstream1: ", paste(Qupstream1, collapse = ", ")) - #message("Qupstream2: ", paste(Qupstream2, collapse = ", ")) - - OutputsModel$Qsim <- OutputsModel$Qsim + - Qupstream1 * HUTRANS[1, upstream_basin] + - Qupstream2 * HUTRANS[2, upstream_basin] + #message("Qupstream[", upstream_basin, "]: ", paste(Qupstream, collapse = ", ")) + OutputsModel$Qsim <- + OutputsModel$Qsim + + Qupstream[2:(1 + LengthTs)] * HUTRANS[1, upstream_basin] + + Qupstream[1:LengthTs] * HUTRANS[2, upstream_basin] } # Warning for negative flows if (any(OutputsModel$Qsim < 0)) { @@ -105,12 +102,7 @@ RunModel_Lag <- function(InputsModel, RunOptions, Param) { if ("StateEnd" %in% RunOptions$Outputs_Sim) { OutputsModel$StateEnd$SD <- lapply(seq(NbUpBasins), function(x) { LengthTs <- tail(RunOptions$IndPeriod_Run,1) - Qupstream <- InputsModel$Qupstream[(LengthTs - floor(PT[x])):LengthTs, x] - if (!is.na(InputsModel$BasinAreas[x])) { - # Upstream flow with area needs to be converted to m3 by time step - Qupstream <- Qupstream * InputsModel$BasinAreas[x] * 1e3 - } - return(Qupstream) + InputsModel$Qupstream[(LengthTs - floor(PT[x])):LengthTs, x] }) #message("StateEnd: ",paste(OutputsModel$StateEnd$SD, collapse = ", ")) } diff --git a/R/utils.R b/R/utils.R index 4cf16933935889bd01ffdb97c01e305beb789d47..5f5650640d91b4b7127bfe621cc23427f6f607d0 100644 --- a/R/utils.R +++ b/R/utils.R @@ -14,35 +14,53 @@ getSD_Ids <- function(InputsModel) { names(InputsModel)[bSDs] } +#' Id of sub-basins not using SD model +#' +#' @param InputsModel `GRiwrmInputsModel` object +#' +#' @return [character] IDs of the sub-basins not using SD model +#' +getNoSD_Ids <- function(InputsModel) { + if (!inherits(InputsModel, "GRiwrmInputsModel")) { + stop("Argument `InputsModel` should be of class GRiwrmInputsModel") + } + bSDs <- sapply(InputsModel, function (IM) { + !inherits(IM, "SD") + }) + names(InputsModel)[bSDs] +} + #' Retrieve data in the model for the current time steps #' #' This function should be call inside a Supervisor #' #' @param loc location of the data -#' @param supervisor `Supervisor` (See [CreateSupervisor]) +#' @param sv a `Supervisor` (See [CreateSupervisor]) #' #' @return [numeric] retrieved data at the location -getDataFromLocation <- function(loc, supervisor) { - if (grep("\\[[0-9]+\\]$", loc)) { +getDataFromLocation <- function(loc, sv) { + if (length(grep("\\[[0-9]+\\]$", loc)) > 0) { stop("Reaching output of other controller is not implemented yet") } else { - supervisor$OutputsModel[[loc]]$Qsim[supervisor$ts.index - 1] + sv$InputsModel[[sv$griwrm$down[sv$griwrm$id == loc]]]$Qupstream[sv$ts.index0 + sv$ts.index - 1, loc] } } #' Write data to model input for the current time step #' -#' @param control [list] A row of the `U` [data.frame] from a `Controller` -#' @param supervisor `Supervisor` (See [CreateSupervisor]) +#' @param control [vector] A row of the `U` [data.frame] from a `Controller` +#' @param sv `Supervisor` (See [CreateSupervisor]) #' #' @return [NULL] -setDataToLocation <- function(control, supervisor) { - node <- supervisor$InputsModel[[control$loc]]$down +setDataToLocation <- function(control, sv) { + message("setDataToLocation[", control[1], "] <- ", control[2]) + node <- sv$griwrm$down[sv$griwrm$id == control[1]] # ! Qupstream contains warm up period and run period => the index is shifted - supervisor$InputsModel[[node]]$Qupstream[supervisor$ts.index0[node] + supervisor$ts.index, control$loc] <- - control$v + sv$InputsModel[[node]]$Qupstream[sv$ts.index0 + sv$ts.index, control[1]] <- + as.numeric(control[2]) + message("setDataToLocation[", control[1], "] <- ", control[2]) } @@ -55,12 +73,12 @@ doSupervision <- function(supervisor) { for (id in names(supervisor$controllers)) { # Read Y from locations in the model supervisor$controllers[[id]]$Y$v <- - sapply(supervisor$controllers[[id]]$Y$loc, getDataFromLocation, supervisor = supervisor) + sapply(supervisor$controllers[[id]]$Y$loc, getDataFromLocation, sv = supervisor) # Run logic supervisor$controllers[[id]]$U$v <- sapply(supervisor$controllers[[id]]$Y$v, supervisor$controllers[[id]]$FUN) # Write U to locations in the model - sapply(supervisor$controllers[[id]]$U, setDataToLocation, supervisor = supervisor) + apply(supervisor$controllers[[id]]$U, 1, setDataToLocation, sv = supervisor) } return() } diff --git a/tests/testthat/test-RunModel.R b/tests/testthat/test-RunModel.R index e32f9311fe752831531848b490d6672ad5f30e1d..ac972f392fdd38029f8b43aac025fcfea7b1487d 100644 --- a/tests/testthat/test-RunModel.R +++ b/tests/testthat/test-RunModel.R @@ -1,44 +1,83 @@ context("RunModel.Supervisor") +# Load data +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 +nTS <- 365 +IndPeriod_Run <- seq( + length(InputsModel[[1]]$DatesR) - nTS + 1, + 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 +) + +# RunModel.GRiwrmInputsModel +Param <- list("54057" = c(0.727, 175.493, -0.082, 0.029, 4.654)) +OM_GriwrmInputs <- RunModel( + InputsModel, + RunOptions = RunOptions, + Param = Param +) + 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 - nTS <- 365 - IndPeriod_Run <- seq( - length(InputsModel[[1]]$DatesR) - nTS + 1, - 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, + sv <- CreateSupervisor(InputsModel) + OM_Supervisor <- RunModel( + sv, RunOptions = RunOptions, Param = Param ) - supervisor <- CreateSupervisor(InputsModel) + expect_equal(OM_Supervisor[["54057"]]$Qsim, OM_GriwrmInputs[["54057"]]$Qsim) +}) + +test_that("RunModelSupervisor with two regulations that cancel each other out should returns same results as RunModel.GRiwrmInputsModel", { + # Add 2 nodes to the network + griwrm2 <- rbind(griwrm, + data.frame( + id = c("R1", "R2"), + down = "54057", + length = 100000, + area = NA, + model = NA + )) + # Add Qobs for the 2 new nodes + Qobs2 <- cbind(Qobs, matrix(data = rep(0, 2*nrow(Qobs)), ncol = 2)) + colnames(Qobs2) <- c(colnames(Qobs2)[1:6], "R1", "R2") + InputsModel <- CreateInputsModel(griwrm2, DatesR, Precip, PotEvap, Qobs2) + sv <- CreateSupervisor(InputsModel) + # Function to withdraw half of the measured flow + fWithdrawal <- function(y) { message("fWithdrawal(", y, ")"); -y/2 } + # Function to release half of the the measured flow + fRelease <- function(y) { message("fWithdrawal(", y, ")"); y/2 } + # Controller that withdraw half of the flow measured at node "54002" at location "R1" + createController(sv, "Withdrawal", Y = c("54002"), U = c("R1"), FUN = fWithdrawal) + # Controller that release half of the flow measured at node "54002" at location "R2" + createController(sv, "Release", Y = c("54002"), U = c("R2"), FUN = fRelease) OM_Supervisor <- RunModel( - supervisor, + sv, RunOptions = RunOptions, Param = Param ) expect_equal(OM_Supervisor[["54057"]]$Qsim, OM_GriwrmInputs[["54057"]]$Qsim) }) +