---
title: "Downstream Accumulation"
author: "dblodgett@usgs.gov"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Downstream Accumulation}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
library(hydroloom)
library(dplyr)

local <- (Sys.getenv("BUILD_VIGNETTES") == "TRUE")

knitr::opts_chunk$set(
  collapse = TRUE,
  warning = FALSE,
  comment = "#>",
  fig.width = 6,
  fig.height = 6,
  fig.align = "center",
  eval = local
)

oldoption <- options(scipen = 9999)
```

# Introduction

A common task in hydrologic network analysis is accumulating a catchment attribute along the network -- summing up a local quantity from headwaters to outlet. For example, adding up the drainage area of every catchment upstream of a given point to get total drainage area, or accumulating precipitation volume to get a basin-wide average.

`accumulate_downstream()` provides three modes for this operation, each handling diversions (non-dendritic flow splits) differently. This article demonstrates all three modes using the sample data included with `hydroloom`. For background on non-dendritic network concepts, see `vignette("non-dendritic")`.

# Sample data

We'll use the "New Hope" sample watershed included with `hydroloom`. First, we prepare a network with the required topology attributes.

```{r}
net <- sf::read_sf(system.file("extdata/new_hope.gpkg", package = "hydroloom"))

# subset to a connected upstream basin
x <- subset_network(net, 8893236)

plot(sf::st_geometry(net))
plot(sf::st_geometry(x), col = "red", add = TRUE)
```

# Three accumulation modes

`accumulate_downstream()` supports three modes that differ in how they handle flow splits (divergences) in the network. The `total` parameter and the presence or absence of a `divergence_fraction` attribute control which mode is used.

## 1. Dendritic (default)

When `total = FALSE` and no `divergence_fraction` attribute is present, diversions receive none of the upstream value -- they are treated as if they are headwaters. This is the simplest mode and is appropriate when working with a strictly dendritic representation of the network or an assumption of 0 contribution of the quantity to diversions is appropriate.

```{r}
# add_toids creates a dendritic edge list (id/toid)
x$dend_totdasqkm <- accumulate_downstream(add_toids(x, return_dendritic = TRUE), "AreaSqKM")

# diversions reset to their own local area
plot(x["dend_totdasqkm"], lwd = x$dend_totdasqkm / 20,
  main = "Dendritic accumulation")
```

## 2. Fractional (divergence apportioned)

When `total = FALSE` and a `divergence_fraction` attribute is present, each downstream path at a divergence receives a fraction of the upstream value. The fractions should sum to 1 across all paths leaving a divergence.

In the example below, we create a simple equal-split fraction: each path gets `1 / n` where `n` is the number of paths leaving a node. More realistic fractions could come from observed flow data or a model.

```{r}
y <- x |>
  group_by(FromNode) |>
  # split evenly among downstream flowlines
  mutate(divergence_fraction = 1 / max(n())) |>
  ungroup()

y$frac_totdasqkm <- accumulate_downstream(y, "AreaSqKM")

# diversions now carry a fraction of upstream area
plot(y["frac_totdasqkm"], lwd = y$frac_totdasqkm / 20,
  main = "Fractional accumulation")

# zoom in to see what's going on at diversions
plot(sf::st_geometry(y[y$COMID %in% c("8893210", "8893222"), ]), col = NA)
plot(y["frac_totdasqkm"], lwd = y$frac_totdasqkm / 20,
  main = "Fractional accumulation", add = TRUE)
```

## 3. Total upstream (no apportionment)

When `total = TRUE`, every downstream path gets the full upstream value. Where diversions rejoin the main flow, the algorithm avoids double counting the shared upstream area. This mode requires `fromnode`, `tonode`, and `divergence` attributes.

This mode reproduces the total drainage area values in NHDPlusV2.

```{r}
z <- x |>
  select(COMID, FromNode, ToNode, Divergence, AreaSqKM, TotDASqKM)

z$tot_totdasqkm <- accumulate_downstream(z, "AreaSqKM", total = TRUE)

plot(z["tot_totdasqkm"], lwd = z$tot_totdasqkm / 20,
  main = "Total upstream accumulation")

# zoom in to see what's going on at diversions
plot(sf::st_geometry(y[y$COMID %in% c("8893210", "8893222"), ]), col = NA)
plot(z["tot_totdasqkm"], lwd = z$tot_totdasqkm / 20,
  main = "Total upstream accumulation", add = TRUE)

# matches NHDPlusV2 total drainage area
any(abs(z$tot_totdasqkm - z$TotDASqKM) > 0.001)
```

# Accumulating derived quantities

Total upstream accumulation is not limited to area. Any catchment-level quantity can be accumulated. A common pattern is to compute a volume (e.g., precipitation depth times area) at each catchment, accumulate the volume downstream, then divide by total drainage area to get an area-weighted average.

The example below demonstrates this with the mean annual precipitation attribute from .

```{r}
suppressWarnings(
ppt7100 <- nhdplusTools::get_catchment_characteristics(c("CAT_PPT7100_ANN", "TOT_PPT7100_ANN"), ids = x$COMID)
)

ppt7100 <- tidyr::pivot_wider(ppt7100, names_from = "characteristic_id", values_from = "characteristic_value")

x <- left_join(x, ppt7100, by = c("COMID" = "comid"))

# mean annual precipitation is already on the sample data
# CAT_PPT7100_ANN is the local catchment mean (mm)
# TOT_PPT7100_ANN is the pre-computed total upstream mean (mm)
w <- x |>
  select(COMID, FromNode, ToNode, Divergence,
    AreaSqKM, CAT_PPT7100_ANN, TOT_PPT7100_ANN)

# local precipitation volume = depth * area
w$cat_ppt_vol <- w$CAT_PPT7100_ANN * w$AreaSqKM

# accumulate volume, then divide by total area for area-weighted mean
w$total_da_sqkm <- accumulate_downstream(w, "AreaSqKM", total = TRUE)
w$tot_ppt_vol <- accumulate_downstream(w, "cat_ppt_vol", total = TRUE)
w$tot_ppt <- w$tot_ppt_vol / w$total_da_sqkm

# note that all differences from the source estimate are within rounding error
range(w$TOT_PPT7100_ANN - w$tot_ppt, na.rm = TRUE)
```

# Choosing a mode

| Mode | `total` | `divergence_fraction` | Use when |
|------|---------|----------------------|----------|
| Dendritic | `FALSE` | absent | Working with a dendritic-only network or diversions can be ignored |
| Fractional | `FALSE` | present | You have or can estimate flow fractions at each divergence |
| Total upstream | `TRUE` | ignored | You need the full upstream value without apportionment (e.g., total drainage area) |

```{r teardown, include=FALSE}
options(oldoption)
```
