Skip to contents

Start with Finding your way around gflow for input conventions, the complete catalog, and migration advice. The example graphs and fields give smaller structural comparisons.

One constructor, five basin families

create.basin.complex() is the recommended constructor for new basin analyses in gflow. Specialized historical/grid constructors have separate schemas. It validates the graph and field once, dispatches to one of five basin construction families, and returns the same basin_complex schema from every family.

method Structure computed Typical use
"trajectory_flow" Flow trajectories and terminal basins Deterministic field-following basins
"superlevel_merge_tree" Plateau-aware graph level-set merge tree Basin births, mergers, and persistence
"geodesic_reachability" Monotone reachability supports Overlapping attraction supports
"rtcb" Relaxed trajectory-constrained basins Bounded departures from monotone paths
"overlap_cell_complex" Ascending-by-descending overlap cells Joint maxima/minima decomposition

The remainder of this vignette uses one small two-dimensional grid only to make the interface executable and inspectable. Scientific basin validation should use the higher-dimensional, scale-path inputs appropriate to the analysis.

Graph and field

The graph is undirected. Each adjacency entry has a parallel edge-length entry, and vertex identifiers are one-based.

edges <- matrix(
  c(
    1, 2, 1, 1, 4, 1, 2, 3, 1, 2, 5, 1,
    3, 6, 1, 4, 5, 1, 4, 7, 1, 5, 6, 1,
    5, 8, 1, 6, 9, 1, 7, 8, 1, 8, 9, 1
  ),
  ncol = 3,
  byrow = TRUE
)

adjacency <- edge_lengths <- vector("list", 9)
for (i in seq_len(nrow(edges))) {
  from <- edges[i, 1]
  to <- edges[i, 2]
  adjacency[[from]] <- c(adjacency[[from]], to)
  adjacency[[to]] <- c(adjacency[[to]], from)
  edge_lengths[[from]] <- c(edge_lengths[[from]], edges[i, 3])
  edge_lengths[[to]] <- c(edge_lengths[[to]], edges[i, 3])
}

field <- c(0, 1, 0, 1, 3, 1, 0, 1, 2)
vertex_mass <- rep(1 / length(field), length(field))

Direction, mass, and density

direction = "max" constructs ascending basins, "min" constructs descending basins, and "both" requests both orientations. The overlap-cell method requires "both".

vertex.mass represents empirical or quadrature support. It is used for basin mass summaries and mass-threshold refinement. vertex.density instead modulates trajectory edge scores when method.params$modulation is "DENSITY" or "DENSITY_EDGELEN". The constructor never substitutes one for the other.

flow <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "trajectory_flow",
  direction = "max",
  vertex.mass = vertex_mass,
  method.params = list(
    modulation = "CLOSEST",
    edge.length.quantile.thld = 1
  )
)

flow$status
#> [1] "ok"
get.basin.table(flow)[
  ,
  c("basin.id", "type", "retained.support.mass")
]
#>              basin.id type retained.support.mass
#> 1 basin_max_v00000005  max             0.7777778
#> 2 basin_max_v00000009  max             0.3333333

For density-modulated flow, provide density explicitly:

density_flow <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "trajectory_flow",
  direction = "max",
  vertex.density = exp(field),
  method.params = list(
    modulation = "DENSITY",
    edge.length.quantile.thld = 1
  )
)
density_flow$status
#> [1] "ok"

Level-set merge tree

The level-set method contracts exact field plateaus and records component births, elder-rule mergers, and component-root deaths. Use it to query connected graph level sets. A field estimated elsewhere at several smoothing scales can also be analyzed separately at each scale.

tree <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "superlevel_merge_tree",
  direction = "max"
)

tree$status
#> [1] "ok"
get.basin.merge.tree(tree, required = TRUE)$merge.table
#>             event.id direction     losing.basin.id  surviving.basin.id
#> 1 merge_max_00000001       max basin_max_v00000009 basin_max_v00000005
#>   merge.plateau.id merge.vertices merge.level birth.level death.level
#> 1 plateau_00000006              6           1           2           1
#>   persistence event.status merge.vertex.ids
#> 1           1           ok                6

Construct one object at every retained scale value, then compare merge tables and memberships between adjacent scale values. The scale parameter belongs to the conditional-expectation estimator, not to create.basin.complex() itself; record it alongside each returned object.

Reachability, RTCB, and overlap cells

reachability <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "geodesic_reachability",
  direction = "max",
  method.params = list(edge.length.quantile.thld = 1)
)

consensus <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "rtcb",
  direction = "max",
  method.params = list(
    edge.length.quantile.thld = 1,
    n.min = 1L,
    m.min = 0
  )
)

cells <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "overlap_cell_complex",
  direction = "both"
)

vapply(
  list(
    geodesic_reachability = reachability,
    rtcb = consensus,
    overlap_cell_complex = cells
  ),
  function(x) x$status,
  character(1)
)
#> geodesic_reachability                  rtcb  overlap_cell_complex 
#>                  "ok"                  "ok"                  "ok"

Membership is not assignment

get.basin.membership() exposes the raw many-to-many relation between vertices and basins. Overlap is allowed. primary.assignment is an optional convenience label selected by the method’s primary.assignment.policy; it does not replace membership and can have NA labels and not_applicable status. A missing assignment is not evidence of missing membership. RTCB currently supplies no backend primary assignment; overlap cells also have no primary assignment.

head(get.basin.membership(reachability))
#>   vertex direction            basin.id membership.weight membership.status
#> 1      1       max basin_max_v00000005               1.0               raw
#> 2      2       max basin_max_v00000005               1.0               raw
#> 3      3       max basin_max_v00000005               0.5               raw
#> 4      3       max basin_max_v00000009               0.5               raw
#> 5      4       max basin_max_v00000005               1.0               raw
#> 6      5       max basin_max_v00000005               1.0               raw
#>   source.stage is.primary vertex.id
#> 1 construction      FALSE         1
#> 2 construction      FALSE         2
#> 3 construction      FALSE         3
#> 4 construction      FALSE         3
#> 5 construction      FALSE         4
#> 6 construction      FALSE         5
get.basin.assignment(reachability)
#>   vertex direction basin.id assignment.weight assignment.status
#> 1      1       max     <NA>                NA    not_applicable
#> 2      2       max     <NA>                NA    not_applicable
#> 3      3       max     <NA>                NA    not_applicable
#> 4      4       max     <NA>                NA    not_applicable
#> 5      5       max     <NA>                NA    not_applicable
#> 6      6       max     <NA>                NA    not_applicable
#> 7      7       max     <NA>                NA    not_applicable
#> 8      8       max     <NA>                NA    not_applicable
#> 9      9       max     <NA>                NA    not_applicable
#>   assignment.policy root.vertex next.vertex vertex.id root.vertex.id
#> 1              none          NA          NA         1           <NA>
#> 2              none          NA          NA         2           <NA>
#> 3              none          NA          NA         3           <NA>
#> 4              none          NA          NA         4           <NA>
#> 5              none          NA          NA         5           <NA>
#> 6              none          NA          NA         6           <NA>
#> 7              none          NA          NA         7           <NA>
#> 8              none          NA          NA         8           <NA>
#> 9              none          NA          NA         9           <NA>
#>   next.vertex.id
#> 1           <NA>
#> 2           <NA>
#> 3           <NA>
#> 4           <NA>
#> 5           <NA>
#> 6           <NA>
#> 7           <NA>
#> 8           <NA>
#> 9           <NA>

Downstream code that needs complete basin supports should use get.basin.membership(). After refinement, use the basin table’s retained.support.vertices for retained support. Code that requires exactly one basin per vertex must choose and record an assignment policy.

Refinement

Refinement stages are controlled with simplify.params. They execute in a fixed order and are recorded in provenance$refinement.stages:

  1. relative-value filtering;
  2. extrema clustering;
  3. geometric filtering;
  4. support filtering; and
  5. expansion.

Stage applicability depends on the construction family. Unsupported combinations fail validation rather than silently doing nothing.

refined <- create.basin.complex(
  adjacency,
  edge_lengths,
  field,
  method = "trajectory_flow",
  direction = "max",
  method.params = list(edge.length.quantile.thld = 1),
  simplify.params = list(
    relative.value = list(enabled = TRUE)
  )
)

refined$provenance$refinement.stages
#>               stage         status   parameters input.basin.count.max
#> 1    relative.value      completed TRUE, 1.....                     2
#> 2 maxima.clustering       disabled  FALSE, 0.15                     2
#> 3 minima.clustering       disabled  FALSE, 0.15                     2
#> 4  geometric.filter       disabled FALSE, 0....                     2
#> 5    support.filter       disabled FALSE, 0....                     2
#> 6         expansion not_applicable FALSE, n....                     2
#>   input.basin.count.min output.basin.count.max output.basin.count.min
#> 1                     0                      2                      0
#> 2                     0                      2                      0
#> 3                     0                      2                      0
#> 4                     0                      2                      0
#> 5                     0                      2                      0
#> 6                     0                      2                      0
#>   input.membership.count output.membership.count retained.basin.ids
#> 1                     10                      10       basin_ma....
#> 2                     10                      10       basin_ma....
#> 3                     10                      10       basin_ma....
#> 4                     10                      10       basin_ma....
#> 5                     10                      10       basin_ma....
#> 6                     10                      10       basin_ma....
#>   removed.basin.ids warnings diagnostics summary.snapshot
#> 1                                            c(max1 =....
#> 2                                            c(max1 =....
#> 3                                            c(max1 =....
#> 4                                            c(max1 =....
#> 5                                            c(max1 =....
#> 6                                            c(max1 =....

Archived objects and retired constructors

The following public constructors are retired and deliberately raise a gflow_basin_lifecycle_error:

Use create.basin.complex() for all new work. as.basin.complex() exists only for compatible archived results. Conversion requires the archived object and its original graph; a field must also be supplied if the archive did not retain one. Conversion provenance records the source class and retained archive so that migration remains auditable.

canonical <- as.basin.complex(
  archived_object,
  adj.list = original_adjacency,
  edge.length.list = original_edge_lengths,
  field = original_field
)

Result checks

Every result carries construction status, resolved parameters, provenance, diagnostics, basin summaries, membership, and optional method-specific structures.

objects <- list(flow, tree, reachability, consensus, cells)
stopifnot(all(vapply(
  objects,
  function(x) inherits(x, "basin_complex") && identical(x$status, "ok"),
  logical(1)
)))

names(flow)
#>  [1] "method"            "direction"         "n.vertices"       
#>  [4] "graph.input"       "field"             "parameters"       
#>  [7] "provenance"        "extrema"           "basin.table"      
#> [10] "membership"        "assignment"        "merge.table"      
#> [13] "trajectory.forest" "cell.complex"      "diagnostics"      
#> [16] "warnings"          "status"            "raw"
flow$provenance$construction
#> $status
#> [1] "complete"
#> 
#> $completed
#> [1] TRUE
#> 
#> $membership.weight.policy
#> [1] "equal_share_set_membership"
#> 
#> $assignment.policy
#> [1] "backend_primary"