Skip to content

Commit

Permalink
Merge branch '168-ungauged-node-cluster-improve-outsider-gauged-node-…
Browse files Browse the repository at this point in the history
…detection' into 'dev'

Resolve "Ungauged node cluster: improve outsider gauged node detection"

Closes #168

See merge request in-wop/airGRiwrm!103
  • Loading branch information
Dorchies David committed Aug 28, 2024
2 parents a3fdf8d + a2a5647 commit abda5de
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 146 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

S3method(Calibration,GRiwrmInputsModel)
S3method(Calibration,InputsModel)
S3method(Calibration,Ungauged)
S3method(ConvertMeteoSD,GRiwrm)
S3method(ConvertMeteoSD,character)
S3method(ConvertMeteoSD,matrix)
Expand Down
14 changes: 6 additions & 8 deletions R/Calibration.GRiwrmInputsModel.R
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ Calibration.GRiwrmInputsModel <- function(InputsModel,
IM <- l$InputsModel
message("Calibration.GRiwrmInputsModel: Processing sub-basins '",
paste(names(IM), collapse = "', '"), "' with '", id, "' as gauged donor...")
IM$FUN_MOD <- "RunModel_Ungauged"
attr(RunOptions[[id]], "GRiwrmRunOptions") <- l$RunOptions
} else {
message("Calibration.GRiwrmInputsModel: Processing sub-basin '", id, "'...")
Expand All @@ -64,9 +63,9 @@ Calibration.GRiwrmInputsModel <- function(InputsModel,
}

if (!is.null(IM$isReservoir) && IM$isReservoir & any(is.na(CalibOptions[[id]]$FixedParam))) {
stop("Parameters of `RunModel_Reservoir` nodes can't be calibrated",
"Fix these parameters by using the command:\n",
"`CalibOptions[[id_of_reservoir_node]]$FixedParam <- c(Vmax, celerity)`")
stop("Parameters of node '", id, "' using `RunModel_Reservoir` can't be calibrated",
"Fix its parameters by using the command:\n",
"`CalibOptions[['", id, "']]$FixedParam <- c(Vmax, celerity)`")
}

if (!hasUngauged && IM$isReceiver) {
Expand All @@ -92,11 +91,10 @@ Calibration.GRiwrmInputsModel <- function(InputsModel,
}

if (hasUngauged) {
# Select nodes with model in the sub-network
g <- attr(IM, "GRiwrm")
Ids <- g$id[!is.na(g$donor) & g$donor == id & g$id != id]
Ids <- names(IM)
Ids <- Ids[Ids != id]
for (uId in Ids) {
if (!IM[[uId]]$isReservoir) {
if (IM[[uId]]$gaugedId == id) {
# Add OutputsCalib for ungauged nodes
OutputsCalib[[uId]] <- list(
ParamFinalR = transferGRparams(InputsModel,
Expand Down
10 changes: 9 additions & 1 deletion R/Calibration.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
#' function using the provided functions.
#'
#' This function can be used either for a catchment (with an \emph{InputsModel}
#' object) or for a network (with a \emph{GRiwrmInputsModel} object)
#' object), for a network (with a \emph{GRiwrmInputsModel} object), or for an
#' ungauged node cluster (with a \emph{Ungauged} object).
#'
#' @param InputsModel \[object of class \emph{InputsModel} or \emph{GRiwrmInputsModel}\] see [CreateInputsModel]
#' @param RunOptions \[object of class \emph{RunOptions} or \emph{GRiwrmRunOptions}\] see [CreateRunOptions]
Expand Down Expand Up @@ -35,3 +36,10 @@
Calibration <- function(InputsModel, ...) {
UseMethod("Calibration", InputsModel)
}

#' @rdname Calibration
#' @export
Calibration.Ungauged <- function(InputsModel, ...) {
InputsModel$FUN_MOD <- "RunModel_Ungauged"
NextMethod()
}
6 changes: 3 additions & 3 deletions R/CreateCalibOptions.R
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ CreateCalibOptions.RunModel_Reservoir <- function(x, FixedParam = NULL, ...) {
CalibOptions$FixedParam <- FixedParam
} else {
warning(
"The node ", x$id, " which uses `RunModel_Reservoir` must have its parameters fixed: ",
"The node '", x$id, "' which uses `RunModel_Reservoir` must have its parameters fixed: ",
"\n",
"You can either fix these parameters afterward by using the command:\n",
"`CalibOptions[[id_of_reservoir_node]]$FixedParam <- c(Vmax, celerity)`\n",
"Or by calling `CreateCalibOptions(InputsModel, FixedParam = list(id_of_reservoir_node = c(Vmax, celerity)))`"
"`CalibOptions[['", x$id, "']]$FixedParam <- c(Vmax, celerity)`\n",
"Or by calling `CreateCalibOptions(InputsModel, FixedParam = list('", x$id, "' = c(Vmax, celerity)))`"
)
}
return(CalibOptions)
Expand Down
137 changes: 80 additions & 57 deletions R/CreateGRiwrm.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
#' - `RunModel_Reservoir` for simulating a reservoir (See: [RunModel_Reservoir])
#' - `Ungauged` for an ungauged node. The sub-basin inherits hydrological model and
#' parameters from a "donor" sub-basin. If not defined by the user in the column `donor`,
#' the donor is automatically set to the first gauged node at downstream
#' the donor is automatically set to the first gauged node at downstream.
#' This set of sub-basins with the same donor downstream then forms an ungauged
#' node cluster that will be calibrated at once.
#' - `NA` for injecting (or abstracting) a flow time series at the location of the node
#' (direct flow injection)
#' - `Diversion` for abstracting a flow time series from an existing node transfer it
Expand Down Expand Up @@ -51,8 +53,9 @@
#' node location in km2
#' * `model` ([character]): hydrological model to use ([NA] for using observed
#' flow instead of a runoff model output)
#' * `donor` ([character]): node used as "donor" for the the model and the
#' calibration parameters.
#' * `donor` ([character]): node used as model and calibration parameter "donor" for
#' ungauged nodes. For other types of nodes, if the donor is different than the
#' id, it indicates that the node is embedded in an ungauged node cluster.
#'
#' @aliases GRiwrm
#' @export
Expand Down Expand Up @@ -105,7 +108,6 @@ CreateGRiwrm <- function(db,

# Set automatic downstream donors for ungauged nodes
griwrm$donor <- setDonor(griwrm)
checkUngaugedCluster(griwrm)

griwrm <- sort(griwrm)

Expand Down Expand Up @@ -248,22 +250,22 @@ nodeError <- function(node, ...) {
#' @return [character] Id of the first node with a model of `FALSE` if not found
#'
#' @noRd
getGaugedId <- function(id, griwrm) {
getDonor <- function(id, griwrm) {
if (isNodeGauged(id, griwrm, skip_reservoirs = TRUE)) {
# Match with a gauged station!
return(id)
} else {
# Otherwise we need to search downstream on the natural network
g2 <- griwrm[getDiversionRows(griwrm, TRUE), ]
id_down <- g2$down[g2$id == id]
if (!is.na(id_down)) {
return(getGaugedId(id_down, griwrm))
node <- g2[g2$id == id, ]
if (!is.na(node$down)) {
return(getDonor(node$down, griwrm))
} else if (length(getDiversionRows(griwrm)) > 0) {
# Search on Diversion
g3 <- griwrm[getDiversionRows(griwrm), ]
id_down <- g3$down[g3$id == id]
if (!is.na(id_down)) {
return(getGaugedId(id_down, griwrm))
node$down <- g3$down[g3$id == id]
if (!is.na(node$down)) {
return(getDonor(node$down, griwrm))
}
}
}
Expand All @@ -285,6 +287,7 @@ getDiversionRows <- function(griwrm, inverse = FALSE) {
}

setDonor <- function(griwrm) {
oDonors <- griwrm$donor
griwrm$donor <- sapply(seq(nrow(griwrm)), function(i) {
if (!is.na(griwrm$donor[i])) {
# Donor set by user
Expand All @@ -295,68 +298,88 @@ setDonor <- function(griwrm) {
if (is.na(model) || model == "Diversion") {
return(as.character(NA))
}
if (model == "RunModel_Reservoir" && is.na(griwrm$down[i])){
# RunModel_Reservoir needs to be its own "donor" only if at downstream
# Otherwise we search the first gauged station downstream to allow
# calibration with ungauged upstream nodes
if (model == "RunModel_Reservoir") {
return(id)
}
gaugedId <- getGaugedId(id, griwrm = griwrm)
if (model != "Ungauged" &&
(is.na(griwrm$down[i]) || !any(!is.na(griwrm$down) & griwrm$down == id))) {
# Downstream or upstream gauged nodes can't be in ungauged node cluster
return(id)
}

gaugedId <- getDonor(id, griwrm = griwrm)
if (gaugedId == FALSE) {
stop("No Gauged node found downstream the node '", id, "'")
}
if (id != gaugedId) {
message("Ungauged node '", id, "' automatically gets the node '", gaugedId, "' as parameter donor")
}
return(gaugedId)
})
d <- sapply(seq(nrow(griwrm)), refineReservoirDonor, griwrm = griwrm)
return(d)
donors <- sapply(
seq(nrow(griwrm)),
FUN = function(i, g) {
d <- refineDonor(i, g)
if (!is.na(d) && (is.na(oDonors[i]) || d != oDonors[i]) && d != g$id[i]) {
if (g$model[i] == "Ungauged") {
message("Ungauged node '", g$id[i], "' automatically gets the node '",
d, "' as parameter donor")
} else if (g$model[i] == "RunModel_Reservoir") {
message("Node '", g$id[i], "' is included in the ungauged node cluster '",
d, "'")

} else {
warning("Node '", g$id[i], "' is included in the ungauged node cluster '",
d, "': it should have fixed parameters at Calibration")
}
}
return(d)
},
g = griwrm)
return(donors)
}

#' Correct donor for reservoir nodes in case they're not in ungauged node clusters
#' Correct donor for gauged nodes inside ungauged node clusters
#'
#' @param i rown number to process in `griwrm`
#' @param griwrm A *GRiwrm* object (See [CreateGRiwrm])
#'
#' @return [character] [vector] of donor ids
#' @noRd
refineReservoirDonor <- function(i, griwrm) {
id <- griwrm$id[i]
if (all(!is.na(griwrm$model[griwrm$id == id]) &
griwrm$model[griwrm$id == id] != "RunModel_Reservoir")) {
return(griwrm$donor[i])
}
upIds <- griwrm$id[!is.na(griwrm$down) & griwrm$down == griwrm$id[i]]
g_up <- griwrm[griwrm$id %in% upIds, ]
if (any(!is.na(g_up$model) & g_up$model == "Ungauged")) {
# Upstream ungauged nodes found: keep downstream donor
donor <- unique(g_up$donor[!is.na(g_up$model) & g_up$model == "Ungauged"])
if (length(donor) > 1) {
stop("Ungauged nodes located upstream the node '", id,
"' cannot have different donors")
refineDonor <- function(i, g) {
if (is.na(g$model[i]) || g$model[i] == "Diversion") return(as.character(NA))
if (g$model[i] == "Ungauged") return(g$donor[i])
id <- g$id[i]
if (is.na(g$donor[i])) g$donor[i] <- id
# Search if the gauged node is in an ungauged node cluster
# Search all ungauged nodes upstream
g2 <- g[!is.na(g$model) & g$model != "Diversion", ] # Remove duplicates for node search
upstreamUngaugedNodes <- sapply(g2$id, function(id2) {
if (g2$model[g2$id == id2] == "Ungauged" && isNodeUpstream(g, id, id2)) {
id2
} else {
NULL
}
})
upstreamUngaugedNodes <- unlist(
upstreamUngaugedNodes[!sapply(upstreamUngaugedNodes, is.null)]
)
if (!is.null(upstreamUngaugedNodes)) {
donors <- setNames(g2$donor, g2$id)
ungaugedDonors <- donors[upstreamUngaugedNodes]
ungaugedDonors <- setdiff(ungaugedDonors, id)
if (length(ungaugedDonors) > 0) {
# Search for donor at downstream (then we are in an ungauged node cluster!)
ungaugedDonors <- ungaugedDonors[sapply(ungaugedDonors, function(x) {
isNodeDownstream(g, id, x)
})]
}
if (length(ungaugedDonors) == 1) {
return(ungaugedDonors)
} else if (length(ungaugedDonors) > 1) {
warning("The node '", id, "' is embedded in several ungauged node clusters: '",
paste(ungaugedDonors, collapse = "', '"), "'\n",
"Calibration of both ungauged node clusters is impossible")
}
if (isNodeDownstream(griwrm, id, donor)) return(donor)
}
# No upstream ungauged nodes: Reservoir is its own donor!
return(griwrm$id[i])
}

checkUngaugedCluster <- function(griwrm) {
# Check presence of gauged nodes inside an ungauged cluster
clusters <- table(griwrm$donor)
clusters <- names(clusters[clusters > 1])
lapply(clusters, function(gaugedId) {
g <- getUngaugedCluster(griwrm, gaugedId)
p <- getAllNodesProperties(griwrm)
upstreamIdsInCluster <- unique(g$id[!g$id %in% g$down])
lapply(g$id, function(id) {
if (id != gaugedId) {
if (p$calibration[p$id == id] == "Gauged" && !id %in% upstreamIdsInCluster) {
stop("The gauged node '", id, "' is located in the cluster of the ungauged",
" nodes calibrated with the node '", gaugedId, "'")
}
}
})
})
# No need to change the pre-defined donor (maybe forced for de Lavenne #157)
return(g$donor[i])
}
16 changes: 11 additions & 5 deletions R/CreateInputsModel.GRiwrm.R
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,12 @@ CreateOneGRiwrmInputsModel <- function(id, griwrm, DatesR, ..., Qinf, Qmin, Qrel

g2 <- griwrm[getDiversionRows(griwrm, TRUE), ]
node <- g2[g2$id == id, ]
FUN_MOD <- g2$model[g2$id == node$donor]
if (node$model == "Ungauged") {
FUN_MOD <- g2$model[g2$id == node$donor]
} else {
FUN_MOD <- node$model
}


# Set hydraulic parameters
UpstreamNodeRows <- which(griwrm$down == id & !is.na(griwrm$down))
Expand Down Expand Up @@ -347,13 +352,14 @@ CreateOneGRiwrmInputsModel <- function(id, griwrm, DatesR, ..., Qinf, Qmin, Qrel
featModel <- .GetFeatModel(InputsModel, IsHyst)
# inUngaugedCluster: Ungauged node with downstream donor
# including reservoirs between ungauged nodes and donor
InputsModel$inUngaugedCluster <- (node$model == "Ungauged" || np$Reservoir) &&
node$id != node$donor &&
InputsModel$inUngaugedCluster <- node$donor != id &&
isNodeDownstream(griwrm, id, node$donor)
# isReceiver: Ungauged node with not downstream donor
InputsModel$isReceiver <- node$model == "Ungauged" &&
!isNodeDownstream(griwrm, id, node$donor)
InputsModel$gaugedId <- node$donor
InputsModel$gaugedId <- ifelse(node$model == "Ungauged",
node$donor,
id)
InputsModel$hasUngaugedNodes <- hasUngaugedNodes(id, griwrm)
InputsModel$model <-
list(
Expand Down Expand Up @@ -452,7 +458,7 @@ getInputBV <- function(x, id, unset = NULL) {
#' @noRd
hasUngaugedNodes <- function(id, griwrm) {
g <- griwrm[!is.na(griwrm$model), ]
idsWithCurrentAsDonor <- g$id[g$id != id & g$donor == id]
idsWithCurrentAsDonor <- g$id[g$id != id & !is.na(g$donor) & g$donor == id]
if (length(idsWithCurrentAsDonor) == 0) return(FALSE)
areNodesUpstream <- sapply(idsWithCurrentAsDonor,
function(x) isNodeUpstream(g, id, x))
Expand Down
Loading

0 comments on commit abda5de

Please sign in to comment.