Skip to content

Domain Decomposition Scheme #79

@dblodgett-usgs

Description

@dblodgett-usgs

Hydrologic Network Domain Decomposition — Design Context Summary

Overview

The scheme decomposes a hydrofabric into domains, each an HY_CatchmentAggregate with exactly one outlet hydro nexus. Domains are of two types. Trunk domains are realized by a major mainstem flowpath; their constituent catchments are the incremental areas along that mainstem. Compact domains are either headwater drainage basins or aggregate catchments whose main waterbody is a segment of a trunk flowpath. Compact domains drape over the trunk — their internal sub-catchments connect to the trunk at multiple nexus points along the mainstem, not just at a single outlet. Internal connectivity within a domain may be non-dendritic (divergences, return flows); inter-domain connectivity is constrained to dendritic pathways, with non-dendritic inter-domain connections handled via a separate override table. The result is a DAG of DAGs: trunk domains form a top-level dendritic DAG, compact domains attach at nexus points along trunks, and the whole structure is grounded in HY_Features concepts (catchment, hydro nexus, mainstem, drainage basin, catchment aggregate).

Terminology

  • Domain — HY_CatchmentAggregate with one outlet nexus; the unit of independent computation.
  • Trunk domain — domain whose mainstem is a major river; receives lateral inflow from compact domains along its length.
  • Compact domain — headwater or tributary aggregate catchment that contributes to a trunk (or to another compact domain).
  • Domain nexus — hydro nexus realized as a hydro location on the trunk flowpath network; the coupling point between domains.
  • Domain graph — DAG whose nodes are domains and whose edges are dendritic inter-domain nexus connections.
  • Override table — relation of non-dendritic inter-domain connections (inter-basin transfers, anthropogenic diversions), applied late-bound after dendritic resolution.
  • Containment — topological enclosure relation between domains or sub-domain catchments, following HY_Features containedCatchment/containingCatchment. Records that a basin is enclosed without asserting hydrologic connectivity.
  • Decomposition — partition of the catchment network into domains, assignment of each catchment to exactly one domain, and identification of domain nexuses and containment relations.
  • Recomposition — forward assembly of domain-level results into a network-wide result by propagating quantities through the domain graph in topological order.

Data Abstraction

The decomposition leans on hydroloom's S3 class hierarchy so that each piece of
the decomposition is carried in the representation that best matches its
connectivity semantics. The relevant classes (see R/hy_classes.R and the
class-level help pages) are:

  • hy — base class; name-aligned tibble with an orig_names attribute.
  • hy_topo — self-referencing edge list (id, toid); unique id; strictly
    dendritic. Inherits from hy.
  • hy_leveledhy_topo enriched with topo_sort, levelpath, and
    levelpath_outlet_id; required for mainstem-aware operations. Inherits from
    hy_topo.
  • hy_node — bipartite feature/nexus graph (id, fromnode, tonode);
    unique id; represents divergences natively through shared node identifiers.
    Inherits from hy.
  • hy_flownetwork — junction table (id, toid with possibly non-unique
    id, optional upmain/downmain); the only edge-list-shaped representation
    that preserves divergences. Does not inherit from hy.

Domain object

An S3 object of class hy_domain wrapping the domain's catchment network
together with metadata. The catchment network is itself a hydroloom object,
and its class depends on domain type and the connectivity it needs to carry:

Attribute Description
domain_id Unique identifier
domain_type "trunk" or "compact"
outlet_nexus_id Outlet hydro nexus
inlet_nexus_ids Inlet domain nexuses (compact domains contributing inflow; for trunks, the set of lateral injection points along the mainstem)
trunk_domain_id For compact domains: receiving trunk. For trunks: self or NA.
containing_domain_id For contained domains: enclosing domain (HY_Features containedCatchment pattern). NA otherwise.
catchments The domain's catchment network — see class selection rules below
topo_sort_offset Global topo_sort base enabling cross-domain ordering

Class selection for catchments:

  • Trunk domains carry a hy_leveled object. Trunks are realized by a
    major mainstem, so levelpath identity is required to recognize the trunk
    flowpath and to position lateral inflows along it. inject_lateral keys off
    levelpath and aggregate_id_measure, both of which presuppose a
    hy_leveled network.
  • Compact domains with purely dendritic internal connectivity carry a
    hy_topo (or hy_leveled if downstream operations need it). This is the
    default and allows sort_network, accumulate_downstream, and the rest of
    the standard hy_topo toolchain to run unchanged inside the domain.
  • Compact domains with non-dendritic internal connectivity carry a
    hy_flownetwork. Divergences, braided channels, and return flows inside a
    compact domain are preserved via upmain/downmain rather than flattened.
    The delegate methods on hy_flownetwork (for add_streamorder,
    accumulate_downstream, add_levelpaths, make_node_topology,
    get_bridge_flowlines) mean most within-domain operations remain callable
    without conversion.
  • Domains that still need divergence-aware graph operations (e.g.,
    add_divergence, subset_network) can additionally materialize a
    hy_node view via make_node_topology; the domain object is agnostic
    about which view a caller wants.

The constructor hy_domain() validates that trunk domains are hy_leveled
and that every compact domain is either hy_topo/hy_leveled or
hy_flownetwork.

Domain graph

The inter-domain graph is a hydroloom edge list in its own right. Because the
dendritic inter-domain graph is a DAG with exactly one outgoing edge per
non-outlet domain, it is carried as a hy_topo (id = domain_id,
toid = downstream domain) — enabling existing hydroloom functions
(sort_network, accumulate_downstream, navigate_hydro_network,
navigate_network_dfs, make_index_ids) to run on it unchanged.

When a decomposition includes non-dendritic inter-domain flow (a compact
domain discharging to multiple trunks, a braid that crosses domain
boundaries), the graph is carried as a hy_flownetwork instead. The class
is chosen automatically by the internal classifier in hy(); downstream
code dispatches on whichever view is present.

The graph edge list columns:

Attribute Description
id Contributing domain (from_domain_id)
toid Receiving domain (to_domain_id)
nexus_id Interface hydro nexus
nexus_position Measure along receiving domain's mainstem (aggregate_id_measure)
relation_type "flow" (dendritic outflow-inflow) or "contained" (topological enclosure)
upmain / downmain Present only in the hy_flownetwork form; annotate main-path at inter-domain divergences

Containment relations (relation_type == "contained") are stored in the
same edge list but excluded from topological operations by default — they
are a property of the decomposition metadata, not of hydrologic flow. The
get_domain_graph() accessor filters by relation_type before returning
the edge list, so callers see a clean flow DAG by default.

Override table

The override table is the late-bound, non-dendritic counterpart to the
domain graph. Structurally it is a hy_flownetwork — an id/toid table
where id may repeat and the upmain/downmain columns, when present,
annotate which connection at a divergence is the main path. Keeping
overrides in hy_flownetwork shape means the same divergence-aware
utilities that operate on intra-domain non-dendritic networks apply
unchanged to inter-domain transfers.

Override-specific columns layered on top of the hy_flownetwork base:

Attribute Description
id / toid Source / sink domain ids (inherited from hy_flownetwork)
source_nexus_id / sink_nexus_id Transfer endpoints
upmain / downmain Optional main-path annotation at source/sink junctions
transfer_type "fraction", "conditional", "scheduled"
transfer_spec List-column of transfer parameters

Decomposition result

S3 object of class domain_decomposition:

  • domains — named list of domain objects
  • domain_graph — inter-domain edge list (both flow and containment relations)
  • overrides — non-dendritic transfer table
  • catchment_domain_index — lookup from catchment id → domain_id
  • nexus_registry — all domain nexuses with realization geometry, measure positions along trunk mainstems, and adjacent domain pairs
  • source_network — reference to the original hy tibble

Operations

Operations partition into three scopes based on what data dependencies they create across domains.

Fully supported in decomposed form (no cross-domain data)

  • Within-domain catchment operations. Any operation local to a single domain's catchment DAG — attribute generation (topo_sort, levelpath, stream_order, Pfafstetter codes), within-domain navigation, landscape characterization. Supports data subsetting and multi-model coupling, where each domain is a self-contained modeling unit.
  • Compact domain resolution. Rainfall-runoff, water quality loading, or any process that transforms distributed inputs into outlet quantities for a compact domain. The internal DAG is self-contained; no trunk information is required. This is the embarrassingly parallel step.
  • Domain graph navigation and ordering. Traversal, topological sort, and dependency analysis on the lightweight domain graph — supports execution scheduling and sensitivity analysis.
  • Domain-level accumulation of summary quantities. Single-value-per-domain quantities (drainage area, mean annual flow, catchment count) accumulate on the domain graph directly via accumulate_domains.

Requiring trunk–compact coordination (inject_lateral pattern)

  • Along-trunk accumulation. Computing cumulative quantities along a trunk flowpath requires knowing, at each trunk segment, the lateral contribution from the surrounding compact domain. The trunk alone contains only its own incremental mainstem catchments; the compact domain contains the lateral tributaries draining into the trunk at specific nexus points. inject_lateral maps compact domain results — which may be spatially distributed within the compact domain, not just a single outlet value — to the specific trunk segments they contribute to, keyed by measure positions in the nexus registry. After injection, standard accumulate_downstream on the trunk proceeds normally.
  • Cross-domain network navigation. Navigation that starts in a compact domain and continues into the trunk (or vice versa) hands off at the domain nexus via the catchment_domain_index and nexus_registry. A bookkeeping operation rather than a data dependency.

Requiring full recomposition

  • Network-wide spatial queries. Arbitrary upstream contributing area, headwater-to-terminal path tracing across multiple domains. Resolved by either full recomposition or lazy on-demand domain loading; the latter is the practical pattern for large networks.
  • Mass-balance verification. Checking that compact domain outflows equal trunk lateral inflows, and that trunk outlet flow equals upstream trunk flow plus lateral inflows minus losses, requires simultaneous access to both scales.
  • Override application. Non-dendritic inter-domain transfers cannot apply until dendritic resolution is complete, because they may connect domains without a direct upstream-downstream relationship. Keeping overrides late-bound preserves the clean DAG-of-DAGs structure for all primary operations.

Contained basins and conditional connectivity

Endorheic basins — and more generally, any hydrologically contained basin whose outflow is intermittent, negligible, or definitionally excluded — introduce a connectivity ambiguity that cuts across the decomposition. A contained basin may sit entirely within a compact domain, span multiple compact domains, or occupy its own compact domain enclosed by another. The decomposition represents this explicitly via the containing_domain_id attribute and "contained" relations in the domain graph, following HY_Features containedCatchment/containingCatchment semantics. This records the enclosure topology without asserting hydrologic connectivity. Whether a contained basin's area and quantities contribute to the containing basin's totals is a use-case-level decision: operations like accumulate_downstream and accumulate_domains accept a policy flag (e.g., include_contained = TRUE/FALSE) that governs traversal. This keeps the domain graph DAG clean — contained basins are leaves or isolated subgraphs, not cycles — while supporting both the "total drainage area includes endorheic" and "contributing area excludes endorheic" interpretations without restructuring the decomposition.

Function Signatures

Decomposition

decompose_network(
  x,                        # hy tibble: full catchment network
  trunk_level = 1,          # stream_level threshold for trunk candidates
  trunk_levelpaths = NULL,  # explicit levelpath IDs (overrides trunk_level)
  min_compact_size = NULL,  # minimum drainage area (sqkm) for compact domain
  outlet_ids = NULL,        # force domain nexuses at these catchment outlets
  overrides = NULL,         # non-dendritic inter-domain connections
  contained_basins = NULL   # basin-to-containing-domain associations
) -> domain_decomposition

Domain access

get_domain(decomposition, domain_id) -> hy tibble
get_domain_graph(decomposition, relations = c("flow", "contained")) -> hy tibble
get_domain_for_catchment(decomposition, catchment_id) -> character
get_nexus_registry(decomposition) -> data.frame
get_overrides(decomposition) -> data.frame
get_containing_domain(decomposition, domain_id) -> character

Domain-level operations

accumulate_domains(
  decomposition,
  domain_values,
  fun = sum,
  include_contained = FALSE    # policy flag for contained basin traversal
) -> named vector

domain_topo_sort(decomposition) -> character vector

navigate_domain_graph(
  decomposition,
  start_domain_id,
  direction = c("up", "down", "upmain", "downmain"),
  include_contained = FALSE,
  max_domains = Inf
) -> character vector

Recomposition

inject_lateral(
  decomposition,
  compact_results,     # named list: compact domain_id → spatially-resolved
                       #   contributions at each trunk nexus point
  trunk_domain_id
) -> data.frame   # trunk catchment network with lateral inflow populated

recompose(
  decomposition,
  domain_results,
  apply_overrides = TRUE,
  include_contained = FALSE,
  check_mass_balance = TRUE
) -> data.frame

Validation

validate_decomposition(
  decomposition,
  check_coverage = TRUE,
  check_dag = TRUE,
  check_outlets = TRUE,
  check_connectivity = TRUE,
  check_containment = TRUE
) -> list(valid = logical, issues = character)

Recomposition in Context

Recomposition is not a reversal of the decomposition — it is the forward operation the decomposition exists to support. The point of decomposing the network is to enable independent, parallel, or modular resolution of subproblems whose results must ultimately stitch into a coherent, network-consistent picture. In practice, recomposition ranges from lightweight (injecting a handful of compact domain outlet flows into a trunk for a single-basin simulation) to heavyweight (assembling time-varying, multi-variate results across thousands of domains for a continental-scale hindcast). The critical design requirement is that the decomposition carries enough metadata — the domain graph topology, the nexus registry with measure positions along trunk flowpaths, the override table, and the containment relations — to make recomposition mechanical rather than interpretive. A consumer of decomposed results should never need to re-derive which compact domain sub-catchments feed which trunk segment, where along the trunk the injection occurs, or whether a contained basin is in or out of scope; that information is encoded in the decomposition object. This makes the decomposition a durable artifact: it can be computed once and used to orchestrate many different simulations, accumulations, or analyses, with recomposition following the same domain graph traversal pattern each time regardless of what quantities are being propagated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions