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_leveled — hy_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.
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
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.Rand theclass-level help pages) are:
hy— base class; name-aligned tibble with anorig_namesattribute.hy_topo— self-referencing edge list (id,toid); uniqueid; strictlydendritic. Inherits from
hy.hy_leveled—hy_topoenriched withtopo_sort,levelpath, andlevelpath_outlet_id; required for mainstem-aware operations. Inherits fromhy_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,toidwith possibly non-uniqueid, optionalupmain/downmain); the only edge-list-shaped representationthat preserves divergences. Does not inherit from
hy.Domain object
An S3 object of class
hy_domainwrapping the domain's catchment networktogether with metadata. The catchment network is itself a hydroloom object,
and its class depends on domain type and the connectivity it needs to carry:
domain_iddomain_type"trunk"or"compact"outlet_nexus_idinlet_nexus_idstrunk_domain_idcontaining_domain_idcatchmentstopo_sort_offsetClass selection for
catchments:hy_leveledobject. Trunks are realized by amajor mainstem, so levelpath identity is required to recognize the trunk
flowpath and to position lateral inflows along it.
inject_lateralkeys offlevelpathandaggregate_id_measure, both of which presuppose ahy_levelednetwork.hy_topo(orhy_leveledif downstream operations need it). This is thedefault and allows
sort_network,accumulate_downstream, and the rest ofthe standard
hy_topotoolchain to run unchanged inside the domain.hy_flownetwork. Divergences, braided channels, and return flows inside acompact domain are preserved via
upmain/downmainrather than flattened.The delegate methods on
hy_flownetwork(foradd_streamorder,accumulate_downstream,add_levelpaths,make_node_topology,get_bridge_flowlines) mean most within-domain operations remain callablewithout conversion.
add_divergence,subset_network) can additionally materialize ahy_nodeview viamake_node_topology; the domain object is agnosticabout which view a caller wants.
The constructor
hy_domain()validates that trunk domains arehy_leveledand that every compact domain is either
hy_topo/hy_leveledorhy_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_flownetworkinstead. The classis chosen automatically by the internal classifier in
hy(); downstreamcode dispatches on whichever view is present.
The graph edge list columns:
idfrom_domain_id)toidto_domain_id)nexus_idnexus_positionrelation_type"flow"(dendritic outflow-inflow) or"contained"(topological enclosure)upmain/downmainhy_flownetworkform; annotate main-path at inter-domain divergencesContainment relations (
relation_type == "contained") are stored in thesame 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 byrelation_typebefore returningthe 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— anid/toidtablewhere
idmay repeat and theupmain/downmaincolumns, when present,annotate which connection at a divergence is the main path. Keeping
overrides in
hy_flownetworkshape means the same divergence-awareutilities that operate on intra-domain non-dendritic networks apply
unchanged to inter-domain transfers.
Override-specific columns layered on top of the
hy_flownetworkbase:id/toidhy_flownetwork)source_nexus_id/sink_nexus_idupmain/downmaintransfer_type"fraction","conditional","scheduled"transfer_specDecomposition result
S3 object of class
domain_decomposition:domains— named list of domain objectsdomain_graph— inter-domain edge list (both flow and containment relations)overrides— non-dendritic transfer tablecatchment_domain_index— lookup from catchment id → domain_idnexus_registry— all domain nexuses with realization geometry, measure positions along trunk mainstems, and adjacent domain pairssource_network— reference to the originalhytibbleOperations
Operations partition into three scopes based on what data dependencies they create across domains.
Fully supported in decomposed form (no cross-domain data)
accumulate_domains.Requiring trunk–compact coordination (
inject_lateralpattern)inject_lateralmaps 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, standardaccumulate_downstreamon the trunk proceeds normally.catchment_domain_indexandnexus_registry. A bookkeeping operation rather than a data dependency.Requiring full recomposition
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_idattribute 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 likeaccumulate_downstreamandaccumulate_domainsaccept 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
Domain access
Domain-level operations
Recomposition
Validation
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.