Cluster Local Extrema Based on Basin Overlap
Source:R/cluster_local_extrema.R
cluster.local.extrema.RdIdentifies clusters of local extrema (either maxima or minima) whose basins
of attraction exhibit substantial overlap or containment relationships. The
function uses basin labels from summary.basins_of_attraction
to ensure consistency with other analyses and returns both numeric cluster
assignments and a convenient cluster membership list.
Usage
cluster.local.extrema(
basins.obj,
adj.list,
edgelen.list,
extrema.type = "max",
overlap.threshold = 0.1
)Arguments
- basins.obj
An object of class
"basins_of_attraction"returned bycompute.basins.of.attraction. This object contains the complete basin structure for both local minima and local maxima.- adj.list
Adjacency list of the graph.
- edgelen.list
A list of numeric vectors containing edge lengths. Element
icontains the lengths of edges incident to vertexi. Must have length equal to the number of vertices in the graph. This parameter is passed tosummary.basins_of_attractionto generate consistent basin labels and compute basin characteristics.- extrema.type
Character string specifying which type of extrema to cluster. Must be either
"max"for local maxima (descending basins) or"min"for local minima (ascending basins). Default is"max".- overlap.threshold
Numeric value in the interval \([0, 1]\) specifying the threshold for the overlap distance below which basins are considered similar and connected in the clustering graph. Smaller values produce more conservative clustering with tighter similarity requirements. For example, a threshold of 0.15 requires that basins share at least 85% overlap relative to the smaller basin. Default is 0.1, corresponding to 90% overlap.
Value
A list containing the clustering results with the following components:
- cluster.assignments
Named integer vector where names are basin labels from
summary.basins_of_attraction(M1,M2, ... for maxima orm1,m2, ... for minima) and values are cluster identifiers. Basins with the same cluster identifier belong to the same cluster.- clusters
Named list where names are cluster identifiers (as character strings) and values are character vectors of basin labels belonging to each cluster. This provides a convenient inverse mapping from clusters to their member basins.
- overlap.distances
Symmetric matrix of pairwise overlap distances between all basins. Rows and columns are labeled with basin identifiers from
summary.basins_of_attraction. Diagonal entries are zero.- basin.vertices
Named list where each element contains the integer vector of vertex indices belonging to the corresponding basin. Names match basin labels from
summary.basins_of_attraction.- overlap.graph
List with components
adj_listandweight_listrepresenting the overlap graph structure. In this graph, vertices correspond to basins and edges connect basins whose overlap distance is below the threshold. Edge weights are the overlap distances. Vertex names in the adjacency list match basin labels.- basin.summary
Data frame from
summary.basins_of_attractionfiltered to include only the extrema of the specified type. Contains detailed information about each basin including label, vertex, value, basin size, and density metrics.- n.clusters
Integer scalar giving the total number of distinct clusters identified.
Details
The clustering problem arises naturally in Morse-Smale analysis when multiple local extrema are positioned close together in the function landscape, creating basins that either overlap substantially or exhibit containment relationships where a smaller basin lies entirely within a larger one. Traditional similarity measures like the Jaccard index may fail to detect such containment patterns because they normalize by the union size rather than the smaller set size.
We address this by employing the overlap coefficient, also known as the Szymkiewicz-Simpson index, which measures basin similarity through the ratio of intersection size to the minimum basin size. For two basins \(A\) and \(B\), the overlap coefficient is defined as $$\text{OC}(A,B) = \frac{|A \cap B|}{\min(|A|, |B|)}$$ This measure equals one when either basin is completely contained in the other, making it ideal for detecting nested or highly overlapping basin structures.
The overlap distance, given by \(d(A,B) = 1 - \text{OC}(A,B)\), converts this similarity into a proper metric. We construct a threshold graph where basins are connected by edges whenever their overlap distance falls below the specified threshold. Connected components of this graph define clusters of similar basins. Within each cluster, basins likely represent the same or closely related features in the underlying function landscape.
The function internally calls summary.basins_of_attraction to
obtain consistent basin labels. These labels follow the convention that minima
are labeled m1, m2, ... in order of increasing function value (so m1 is the
global minimum), while maxima are labeled M1, M2, ... in order of decreasing
function value (so M1 is the global maximum). This labeling scheme ensures
that clustering results align with other basin analyses.
The function operates exclusively on basins of a single extremum type to maintain the interpretability of clusters. Mixing ascending and descending basins would conflate fundamentally different topological features.
The overlap graph returned by the function can be visualized using standard graph visualization tools to examine the structure of basin relationships.
See also
compute.basins.of.attraction for computing basins of attraction,
summary.basins_of_attraction for generating basin summaries with
consistent labels,
create.basin.cx for comprehensive basin complex analysis with
automatic clustering and simplification
Examples
if (FALSE) { # \dontrun{
# Compute basins of attraction for a function on a graph
basins <- compute.basins.of.attraction(adj.list, adj.list, weight.list, y)
# Generate basin summary with consistent labels
basin.df <- summary(basins, adj.list, edgelen.list)
print(basin.df)
# Cluster local maxima using the same labels
max.clusters <- cluster.local.extrema(basins,
adj.list,
edgelen.list,
extrema.type = "max",
overlap.threshold = 0.15)
# Report clustering results
cat("Found", max.clusters$n.clusters, "clusters among",
nrow(max.clusters$basin.summary), "maxima\n")
# Examine cluster assignments
print(max.clusters$cluster.assignments)
# View clusters and their members
print(max.clusters$clusters)
# Identify multi-basin clusters
multi.basin.clusters <- Filter(function(x) length(x) > 1,
max.clusters$clusters)
if (length(multi.basin.clusters) > 0) {
cat("\nClusters with multiple basins:\n")
for (cluster.id in names(multi.basin.clusters)) {
cat("Cluster", cluster.id, ":",
paste(multi.basin.clusters[[cluster.id]], collapse = ", "), "\n")
}
}
# Inspect basin characteristics for clustered maxima
clustered.labels <- unlist(multi.basin.clusters)
clustered.basins <- max.clusters$basin.summary[
max.clusters$basin.summary$label %in% clustered.labels,
]
print(clustered.basins[, c("label", "vertex", "value", "basin.size")])
# Check overlap distances
print(round(max.clusters$overlap.distances, 3))
# Visualize the overlap graph
library(igraph)
n.basins <- length(max.clusters$overlap.graph$adj_list)
edge.list <- matrix(nrow = 0, ncol = 2)
edge.weights <- numeric(0)
for (i in seq_len(n.basins)) {
neighbors <- max.clusters$overlap.graph$adj_list[[i]]
if (length(neighbors) > 0) {
for (j in seq_along(neighbors)) {
neighbor <- neighbors[j]
if (i < neighbor) { # Avoid duplicate edges
edge.list <- rbind(edge.list, c(i, neighbor))
edge.weights <- c(edge.weights,
max.clusters$overlap.graph$weight_list[[i]][j])
}
}
}
}
if (nrow(edge.list) > 0) {
g <- graph_from_edgelist(edge.list, directed = FALSE)
V(g)$name <- names(max.clusters$overlap.graph$adj_list)
E(g)$weight <- edge.weights
plot(g, vertex.label = V(g)$name,
edge.label = round(E(g)$weight, 2),
main = "Basin Overlap Graph")
}
# Similarly cluster local minima
min.clusters <- cluster.local.extrema(basins,
adj.list,
edgelen.list,
extrema.type = "min",
overlap.threshold = 0.15)
print(min.clusters$clusters)
} # }