diff --git a/R/aaa-cliques.R b/R/aaa-cliques.R index 83f6472fdf4..42bcaf53a44 100644 --- a/R/aaa-cliques.R +++ b/R/aaa-cliques.R @@ -107,7 +107,7 @@ cliques_impl <- function( max ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -163,7 +163,7 @@ largest_cliques_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -303,7 +303,7 @@ maximal_cliques_impl <- function( max_size ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -338,7 +338,7 @@ maximal_cliques_subset_impl <- function( max_size ) if (igraph_opt("return.vs.es")) { - res$res <- lapply(res$res, unsafe_create_vs, graph = graph, verts = V(graph)) + res$res <- create_vs_list(graph, res$res) } if (!details) { res <- res$res @@ -383,7 +383,7 @@ independent_vertex_sets_impl <- function( max_size ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -420,7 +420,7 @@ largest_independent_vertex_sets_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -438,7 +438,7 @@ maximal_independent_vertex_sets_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -468,7 +468,7 @@ largest_weighted_cliques_impl <- function( vertex_weights ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -531,7 +531,7 @@ weighted_cliques_impl <- function( maximal ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } diff --git a/R/aaa-cycles.R b/R/aaa-cycles.R index f7671c6bde0..c97622ce2f7 100644 --- a/R/aaa-cycles.R +++ b/R/aaa-cycles.R @@ -380,7 +380,7 @@ simple_cycles_impl <- function( max_cycle_length ) if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vertices <- create_vs_list(graph, res$vertices) } if (igraph_opt("return.vs.es")) { res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) diff --git a/R/aaa-flows.R b/R/aaa-flows.R index 44f5b8faf0a..e01723400b3 100644 --- a/R/aaa-flows.R +++ b/R/aaa-flows.R @@ -17,7 +17,7 @@ cohesive_blocks_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res$blocks <- lapply(res$blocks, unsafe_create_vs, graph = graph, verts = V(graph)) + res$blocks <- create_vs_list(graph, res$blocks) } class(res) <- "cohesiveBlocks" res @@ -176,7 +176,7 @@ all_st_cuts_impl <- function( res$cuts <- lapply(res$cuts, unsafe_create_es, graph = graph, es = E(graph)) } if (igraph_opt("return.vs.es")) { - res$partition1s <- lapply(res$partition1s, unsafe_create_vs, graph = graph, verts = V(graph)) + res$partition1s <- create_vs_list(graph, res$partition1s) } res } @@ -225,7 +225,7 @@ all_st_mincuts_impl <- function( res$cuts <- lapply(res$cuts, unsafe_create_es, graph = graph, es = E(graph)) } if (igraph_opt("return.vs.es")) { - res$partition1s <- lapply(res$partition1s, unsafe_create_vs, graph = graph, verts = V(graph)) + res$partition1s <- create_vs_list(graph, res$partition1s) } res } diff --git a/R/aaa-graphlets.R b/R/aaa-graphlets.R index e9604885b0c..2a156b1c273 100644 --- a/R/aaa-graphlets.R +++ b/R/aaa-graphlets.R @@ -26,7 +26,7 @@ graphlets_candidate_basis_impl <- function( weights ) if (igraph_opt("return.vs.es")) { - res$cliques <- lapply(res$cliques, unsafe_create_vs, graph = graph, verts = V(graph)) + res$cliques <- create_vs_list(graph, res$cliques) } res } @@ -57,7 +57,7 @@ graphlets_impl <- function( niter ) if (igraph_opt("return.vs.es")) { - res$cliques <- lapply(res$cliques, unsafe_create_vs, graph = graph, verts = V(graph)) + res$cliques <- create_vs_list(graph, res$cliques) } res } diff --git a/R/aaa-isomorphism.R b/R/aaa-isomorphism.R index 343419a9d95..af52bd6da37 100644 --- a/R/aaa-isomorphism.R +++ b/R/aaa-isomorphism.R @@ -174,7 +174,7 @@ automorphism_group_impl <- function( sh ) if (igraph_opt("return.vs.es")) { - res$generators <- lapply(res$generators, unsafe_create_vs, graph = graph, verts = V(graph)) + res$generators <- create_vs_list(graph, res$generators) } if (!details) { res <- res$generators diff --git a/R/aaa-paths.R b/R/aaa-paths.R index 35daacbeb2d..95a29f097c4 100644 --- a/R/aaa-paths.R +++ b/R/aaa-paths.R @@ -784,7 +784,7 @@ get_all_shortest_paths_dijkstra_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vpaths <- lapply(res$vpaths, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vpaths <- create_vs_list(graph, res$vpaths) } if (igraph_opt("return.vs.es")) { res$epaths <- lapply(res$epaths, unsafe_create_es, graph = graph, es = E(graph)) @@ -826,7 +826,7 @@ get_all_shortest_paths_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vpaths <- lapply(res$vpaths, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vpaths <- create_vs_list(graph, res$vpaths) } if (igraph_opt("return.vs.es")) { res$epaths <- lapply(res$epaths, unsafe_create_es, graph = graph, es = E(graph)) @@ -931,7 +931,7 @@ get_k_shortest_paths_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vpaths <- lapply(res$vpaths, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vpaths <- create_vs_list(graph, res$vpaths) } if (igraph_opt("return.vs.es")) { res$epaths <- lapply(res$epaths, unsafe_create_es, graph = graph, es = E(graph)) @@ -1206,7 +1206,7 @@ get_shortest_paths_bellman_ford_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vertices <- create_vs_list(graph, res$vertices) } if (igraph_opt("return.vs.es")) { res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) @@ -1258,7 +1258,7 @@ get_shortest_paths_dijkstra_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vertices <- create_vs_list(graph, res$vertices) } if (igraph_opt("return.vs.es")) { res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) @@ -1300,7 +1300,7 @@ get_shortest_paths_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vertices <- create_vs_list(graph, res$vertices) } if (igraph_opt("return.vs.es")) { res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) @@ -1453,7 +1453,7 @@ get_widest_paths_impl <- function( mode ) if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + res$vertices <- create_vs_list(graph, res$vertices) } if (igraph_opt("return.vs.es")) { res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) diff --git a/R/aaa-separators.R b/R/aaa-separators.R index ef97067ea63..5705728e6a8 100644 --- a/R/aaa-separators.R +++ b/R/aaa-separators.R @@ -14,7 +14,7 @@ all_minimal_st_separators_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } @@ -86,7 +86,7 @@ minimum_size_separators_impl <- function( graph ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } diff --git a/R/aaa-structural.R b/R/aaa-structural.R index de9543e847b..e8adb9454ad 100644 --- a/R/aaa-structural.R +++ b/R/aaa-structural.R @@ -336,7 +336,7 @@ biconnected_components_impl <- function( res$component_edges <- lapply(res$component_edges, unsafe_create_es, graph = graph, es = E(graph)) } if (igraph_opt("return.vs.es")) { - res$components <- lapply(res$components, unsafe_create_vs, graph = graph, verts = V(graph)) + res$components <- create_vs_list(graph, res$components) } if (igraph_opt("return.vs.es")) { res$articulation_points <- create_vs(graph, res$articulation_points) @@ -1290,7 +1290,7 @@ neighborhood_impl <- function( mindist ) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } diff --git a/R/cliques.R b/R/cliques.R index 9005ce8f8cc..ff26cf0f022 100644 --- a/R/cliques.R +++ b/R/cliques.R @@ -358,7 +358,7 @@ max_cliques <- function( res <- lapply(res, function(x) x + 1) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res @@ -582,7 +582,7 @@ ivs <- function(graph, min = NULL, max = NULL) { res <- lapply(res, `+`, 1) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res diff --git a/R/cohesive.blocks.R b/R/cohesive.blocks.R index b969a8fee76..e566e9dea6c 100644 --- a/R/cohesive.blocks.R +++ b/R/cohesive.blocks.R @@ -362,12 +362,7 @@ cohesive_blocks <- function(graph, labels = TRUE) { res$labels <- V(graph)$name } if (igraph_opt("return.vs.es")) { - res$blocks <- lapply( - res$blocks, - unsafe_create_vs, - graph = graph, - verts = V(graph) - ) + res$blocks <- create_vs_list(graph, res$blocks) } res$vcount <- vcount(graph) diff --git a/R/components.R b/R/components.R index b4709d7a49a..dc502c78b0a 100644 --- a/R/components.R +++ b/R/components.R @@ -343,12 +343,7 @@ biconnected_components <- function(graph) { res$component.edges <- res$component_edges } if (igraph_opt("return.vs.es")) { - res$components <- lapply( - res$components, - unsafe_create_vs, - graph = graph, - verts = V(graph) - ) + res$components <- create_vs_list(graph, res$components) } if (igraph_opt("return.vs.es")) { res$articulation_points <- create_vs(graph, res$articulation_points) diff --git a/R/conversion.R b/R/conversion.R index f6e6cd062c5..4af3dce0c42 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -658,7 +658,7 @@ as_adj_list <- function( res <- .Call(Rx_igraph_get_adjlist, graph, mode, loops, multiple) res <- lapply(res, `+`, 1) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } if (is_named(graph)) { names(res) <- V(graph)$name diff --git a/R/interface.R b/R/interface.R index bc528bf78d0..8cbc5f21245 100644 --- a/R/interface.R +++ b/R/interface.R @@ -641,7 +641,7 @@ adjacent_vertices <- function(graph, v, mode = c("out", "in", "all", "total")) { res <- lapply(res, `+`, 1) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } if (is_named(graph)) { diff --git a/R/iterators.R b/R/iterators.R index 78d20e0d1a4..af0de8e3500 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -87,6 +87,22 @@ identical_graphs <- function(g1, g2, attrs = TRUE) { .Call(Rx_igraph_identical_graphs, g1, g2, as.logical(attrs)) } +# Build the `names` attribute of a vertex/edge sequence lazily. +# +# `source` is the graph's full vertex/edge name vector (shared by reference +# across all sequences of the graph) and `idx` is a 1-based index into it. +# The result is an ALTREP string vector that only materializes the actual +# names when they are first accessed (printing, named indexing, `as_ids()`), +# so constructing a sequence stays cheap even when many of them are returned +# at once (e.g. `max_cliques()`). Subsetting it stays lazy too, via the +# class's `Extract_subset` method. Returns `NULL` when there is no source. +lazy_index_names <- function(source, idx) { + if (is.null(source)) { + return(NULL) + } + .Call(Rx_igraph_lazy_names, source, idx) +} + add_vses_graph_ref <- function(vses, graph) { ref <- get_vs_ref(graph) if (!is.null(ref)) { @@ -253,7 +269,7 @@ V <- function(graph) { res <- seq_len(vcount(graph)) if (is_named(graph)) { - names(res) <- vertex_attr(graph)$name + names(res) <- lazy_index_names(vertex_attr(graph)$name, res) } class(res) <- "igraph.vs" res <- set_complete_iterator(res) @@ -276,8 +292,46 @@ unsafe_create_vs <- function(graph, idx, verts = NULL) { if (is.null(verts)) { verts <- V(graph) } - res <- simple_vs_index(verts, idx, na_ok = TRUE) - add_vses_graph_ref(res, graph) + # `idx` are vertex IDs straight from C, and `verts` is the full `V(graph)`, + # so `verts[idx]` would just be `idx` again -- skip that copy and use the + # IDs directly as the payload. Names are taken lazily from `verts` (an + # ALTREP that composes in O(1) under subsetting), and the graph reference is + # shared from `verts`. All attributes are set in one `attributes<-` call to + # avoid the per-object shallow copies that dominate when many sequences are + # built (e.g. `max_cliques()`). + nms <- attr(verts, "names") + res <- as.integer(idx) + attributes(res) <- list( + names = if (is.null(nms)) NULL else nms[idx], + class = "igraph.vs", + env = attr(verts, "env"), + graph = attr(verts, "graph") + ) + res +} + +# Build a list of vertex sequences from a list of vertex-ID vectors. +# +# This is the batch form of `unsafe_create_vs()` and replaces the +# `lapply(idx_list, unsafe_create_vs, graph = graph, verts = V(graph))` +# pattern. The per-graph work -- `V(graph)`, the shared weak reference, the +# graph id and the lazy name source -- is hoisted out of the loop, so each +# sequence only costs one `as.integer()` and one `attributes<-`. This is what +# brings construction of many sequences (e.g. `max_cliques()`) down close to +# the cost of returning bare numeric indices. +create_vs_list <- function(graph, idx_list) { + # `verts <- V(graph)` is what mints the single shared weak reference and graph + # id; build it once and hand the pieces to C, which runs the per-element + # construction loop (payload coercion, lazy-names ALTREP, attribute setting) + # without any per-object R overhead. + verts <- V(graph) + .Call( + Rx_igraph_vs_list, + idx_list, + if (is_named(graph)) vertex_attr(graph)$name else NULL, + attr(verts, "env"), + attr(verts, "graph") + ) } # Internal function to quickly convert integer vectors to igraph.es @@ -288,8 +342,9 @@ unsafe_create_es <- function(graph, idx, es = NULL) { if (is.null(es)) { es <- E(graph) } - res <- simple_es_index(es, idx, na_ok = TRUE) - add_vses_graph_ref(res, graph) + # `simple_es_index()` already carries the graph reference over from `es`, + # so the weak reference built once by `E(graph)` is shared across calls. + simple_es_index(es, idx, na_ok = TRUE) } @@ -381,7 +436,7 @@ E <- function(graph, P = NULL, path = NULL, directed = TRUE) { } if ("name" %in% edge_attr_names(graph)) { - names(res) <- edge_attr(graph)$name[res] + names(res) <- lazy_index_names(edge_attr(graph)$name, res) } if (is_named(graph)) { el <- ends(graph, es = res) @@ -404,7 +459,19 @@ simple_vs_index <- function(x, i, na_ok = FALSE) { if (!na_ok && anyNA(res)) { cli::cli_abort("Unknown vertex selected.") } - class(res) <- "igraph.vs" + # Set every attribute in a single `attributes<-` call rather than one + # `attr<-`/`class<-` at a time: each incremental assignment shallow-copies + # the vector, and that copying dominates when many sequences are built + # (e.g. `max_cliques()`). `names` is carried over from the subset above (a + # lazy ALTREP, or NULL); env/graph are carried from `x`, mirroring + # `simple_es_index()`, so sequences derived from one `V(graph)` share its + # weak reference instead of each minting a fresh one. + attributes(res) <- list( + names = attr(res, "names"), + class = "igraph.vs", + env = attr(x, "env"), + graph = attr(x, "graph") + ) res } diff --git a/R/paths.R b/R/paths.R index bc0468425b9..0992ff06de7 100644 --- a/R/paths.R +++ b/R/paths.R @@ -130,7 +130,7 @@ all_simple_paths <- function( res <- get.all.simple.paths.pp(res) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res } diff --git a/R/structural-properties.R b/R/structural-properties.R index 9182950592e..8826ba1f9cd 100644 --- a/R/structural-properties.R +++ b/R/structural-properties.R @@ -1355,12 +1355,7 @@ shortest_paths <- function( if (igraph_opt("return.vs.es")) { if (!is.null(res$vpath)) { - res$vpath <- lapply( - res$vpath, - unsafe_create_vs, - graph = graph, - verts = V(graph) - ) + res$vpath <- create_vs_list(graph, res$vpath) } if (!is.null(res$epath)) { res$epath <- lapply( @@ -1426,12 +1421,7 @@ all_shortest_paths <- function( } if (igraph_opt("return.vs.es")) { - res$vpaths <- lapply( - res$vpaths, - unsafe_create_vs, - graph = graph, - verts = V(graph) - ) + res$vpaths <- create_vs_list(graph, res$vpaths) } # Transitional, eventually, remove $res @@ -2181,7 +2171,7 @@ ego <- function( res <- lapply(res, function(x) x + 1) if (igraph_opt("return.vs.es")) { - res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph)) + res <- create_vs_list(graph, res) } res diff --git a/man/biconnected.components.Rd b/man/biconnected.components.Rd index 43d62a46e92..56f6906c45b 100644 --- a/man/biconnected.components.Rd +++ b/man/biconnected.components.Rd @@ -17,7 +17,7 @@ it is directed.} consistent API. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_biconnected_components}{\code{biconnected_components()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_biconnected_components}{\code{biconnected_components()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \keyword{internal} diff --git a/man/biconnected_components.Rd b/man/biconnected_components.Rd index edab1796bfc..3ee2ebdfc8d 100644 --- a/man/biconnected_components.Rd +++ b/man/biconnected_components.Rd @@ -46,7 +46,7 @@ that this is not true for vertices: the same vertex can be part of many biconnected components. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_biconnected_components}{\code{biconnected_components()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_biconnected_components}{\code{biconnected_components()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ diff --git a/man/graphlet_basis.Rd b/man/graphlet_basis.Rd index 045b239d4b0..9c5fe9dda58 100644 --- a/man/graphlet_basis.Rd +++ b/man/graphlet_basis.Rd @@ -76,7 +76,7 @@ the algorithm, and they are useful if the user wishes to perform them individually: \code{graphlet_basis()} and \code{graphlet_proj()}. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_candidate_basis}{\code{graphlets_candidate_basis()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_project}{\code{graphlets_project()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets}{\code{graphlets()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_candidate_basis}{\code{graphlets_candidate_basis()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_project}{\code{graphlets_project()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets}{\code{graphlets()}} } \examples{ diff --git a/man/graphlets.candidate.basis.Rd b/man/graphlets.candidate.basis.Rd index b72de6dc592..7c2f37be8d0 100644 --- a/man/graphlets.candidate.basis.Rd +++ b/man/graphlets.candidate.basis.Rd @@ -21,7 +21,7 @@ attribute is used.} consistent API. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_candidate_basis}{\code{graphlets_candidate_basis()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Graphlets.html#igraph_graphlets_candidate_basis}{\code{graphlets_candidate_basis()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \keyword{internal} diff --git a/man/k_shortest_paths.Rd b/man/k_shortest_paths.Rd index 1dfe09896ef..dbeaaa71f66 100644 --- a/man/k_shortest_paths.Rd +++ b/man/k_shortest_paths.Rd @@ -56,7 +56,7 @@ vertex in order of increasing length. Currently this function uses Yen's algorithm. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_k_shortest_paths}{\code{get_k_shortest_paths()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_k_shortest_paths}{\code{get_k_shortest_paths()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \references{ diff --git a/man/simple_cycles.Rd b/man/simple_cycles.Rd index 2d769957c35..fe6deab4f1a 100644 --- a/man/simple_cycles.Rd +++ b/man/simple_cycles.Rd @@ -59,7 +59,7 @@ have exponentially many cycles and the presence of multi-edges exacerbates this combinatorial explosion. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Cycles.html#igraph_simple_cycles}{\code{simple_cycles()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Cycles.html#igraph_simple_cycles}{\code{simple_cycles()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ diff --git a/man/stCuts.Rd b/man/stCuts.Rd index 437d55c739f..8460ca93d26 100644 --- a/man/stCuts.Rd +++ b/man/stCuts.Rd @@ -20,7 +20,7 @@ stCuts(graph, source, target) consistent API. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_cuts}{\code{all_st_cuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_cuts}{\code{all_st_cuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \keyword{internal} diff --git a/man/stMincuts.Rd b/man/stMincuts.Rd index 855722247d1..993071048de 100644 --- a/man/stMincuts.Rd +++ b/man/stMincuts.Rd @@ -26,7 +26,7 @@ here.} consistent API. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_mincuts}{\code{all_st_mincuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_mincuts}{\code{all_st_mincuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \keyword{internal} diff --git a/man/st_cuts.Rd b/man/st_cuts.Rd index 1a19298155f..b4f5ac16afd 100644 --- a/man/st_cuts.Rd +++ b/man/st_cuts.Rd @@ -38,7 +38,7 @@ removing these edges from \eqn{G} there is no directed path from \eqn{s} to \eqn{t}. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_cuts}{\code{all_st_cuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_cuts}{\code{all_st_cuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ diff --git a/man/st_min_cuts.Rd b/man/st_min_cuts.Rd index 073ea57f62f..1a550a0a5b3 100644 --- a/man/st_min_cuts.Rd +++ b/man/st_min_cuts.Rd @@ -54,7 +54,7 @@ simply the number of edges. An \eqn{(s,t)}-cut is minimum if it is of the smallest possible size. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_mincuts}{\code{all_st_mincuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Flows.html#igraph_all_st_mincuts}{\code{all_st_mincuts()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ diff --git a/src/cpp11.cpp b/src/cpp11.cpp index dfbdf12f4fc..f7e3c51694b 100644 --- a/src/cpp11.cpp +++ b/src/cpp11.cpp @@ -564,6 +564,7 @@ extern SEXP Rx_igraph_layout_kamada_kawai_3d(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, extern SEXP Rx_igraph_layout_lgl(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_layout_merge_dla(SEXP, SEXP); extern SEXP Rx_igraph_layout_reingold_tilford(SEXP, SEXP, SEXP, SEXP, SEXP); +extern SEXP Rx_igraph_lazy_names(SEXP, SEXP); extern SEXP Rx_igraph_make_weak_ref(SEXP, SEXP, SEXP); extern SEXP Rx_igraph_maximal_cliques(SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_maximal_cliques_count(SEXP, SEXP, SEXP, SEXP); @@ -593,15 +594,14 @@ extern SEXP Rx_igraph_transitivity_local_undirected_all(SEXP, SEXP); extern SEXP Rx_igraph_union(SEXP, SEXP); extern SEXP Rx_igraph_vcount(SEXP); extern SEXP Rx_igraph_vs_adj(SEXP, SEXP, SEXP, SEXP); +extern SEXP Rx_igraph_vs_list(SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_vs_nei(SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_walktrap_community(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_weak_ref_key(SEXP); -extern SEXP Rx_igraph_weak_ref_run_finalizer(SEXP); extern SEXP Rx_igraph_weak_ref_value(SEXP); extern SEXP Rx_igraph_write_graph_dimacs(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_write_graph_lgl(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP Rx_igraph_write_graph_ncol(SEXP, SEXP, SEXP, SEXP); -extern SEXP UUID_gen(SEXP); static const R_CallMethodDef CallEntries[] = { {"R_igraph_add_edge", (DL_FUNC) &R_igraph_add_edge, 3}, @@ -1146,6 +1146,7 @@ static const R_CallMethodDef CallEntries[] = { {"Rx_igraph_layout_lgl", (DL_FUNC) &Rx_igraph_layout_lgl, 8}, {"Rx_igraph_layout_merge_dla", (DL_FUNC) &Rx_igraph_layout_merge_dla, 2}, {"Rx_igraph_layout_reingold_tilford", (DL_FUNC) &Rx_igraph_layout_reingold_tilford, 5}, + {"Rx_igraph_lazy_names", (DL_FUNC) &Rx_igraph_lazy_names, 2}, {"Rx_igraph_make_weak_ref", (DL_FUNC) &Rx_igraph_make_weak_ref, 3}, {"Rx_igraph_maximal_cliques", (DL_FUNC) &Rx_igraph_maximal_cliques, 4}, {"Rx_igraph_maximal_cliques_count", (DL_FUNC) &Rx_igraph_maximal_cliques_count, 4}, @@ -1175,15 +1176,14 @@ static const R_CallMethodDef CallEntries[] = { {"Rx_igraph_union", (DL_FUNC) &Rx_igraph_union, 2}, {"Rx_igraph_vcount", (DL_FUNC) &Rx_igraph_vcount, 1}, {"Rx_igraph_vs_adj", (DL_FUNC) &Rx_igraph_vs_adj, 4}, + {"Rx_igraph_vs_list", (DL_FUNC) &Rx_igraph_vs_list, 4}, {"Rx_igraph_vs_nei", (DL_FUNC) &Rx_igraph_vs_nei, 4}, {"Rx_igraph_walktrap_community", (DL_FUNC) &Rx_igraph_walktrap_community, 6}, {"Rx_igraph_weak_ref_key", (DL_FUNC) &Rx_igraph_weak_ref_key, 1}, - {"Rx_igraph_weak_ref_run_finalizer", (DL_FUNC) &Rx_igraph_weak_ref_run_finalizer, 1}, {"Rx_igraph_weak_ref_value", (DL_FUNC) &Rx_igraph_weak_ref_value, 1}, {"Rx_igraph_write_graph_dimacs", (DL_FUNC) &Rx_igraph_write_graph_dimacs, 5}, {"Rx_igraph_write_graph_lgl", (DL_FUNC) &Rx_igraph_write_graph_lgl, 5}, {"Rx_igraph_write_graph_ncol", (DL_FUNC) &Rx_igraph_write_graph_ncol, 4}, - {"UUID_gen", (DL_FUNC) &UUID_gen, 1}, {"_igraph_getsphere", (DL_FUNC) &_igraph_getsphere, 7}, {"_igraph_igraph_hcass2", (DL_FUNC) &_igraph_igraph_hcass2, 3}, {NULL, NULL, 0} diff --git a/src/rinterface_extra.c b/src/rinterface_extra.c index e616295c64f..4f25e94c697 100644 --- a/src/rinterface_extra.c +++ b/src/rinterface_extra.c @@ -2501,6 +2501,182 @@ static void *Rx_igraph_altrep_to(SEXP vec, Rboolean writeable) { static R_altrep_class_t Rx_igraph_altrep_from_class; static R_altrep_class_t Rx_igraph_altrep_to_class; +/* ------------------------------------------------------------------------ + * Lazy names for vertex/edge sequences. + * + * A vertex/edge sequence carries its `names` attribute as an instance of this + * ALTREP string class instead of a materialized character vector. The actual + * names are only built when an element is touched (printing, named indexing, + * as_ids()), not when the sequence is constructed -- which is the common case + * for functions that return tens of thousands of sequences (e.g. max_cliques). + * + * data1 = list(source, idx): `source` is the graph's full vertex/edge name + * vector (shared by reference across all sequences of a graph) and `idx` is a + * 1-based integer index into `source`. data2 caches the materialized STRSXP. + * ------------------------------------------------------------------------ */ +static R_altrep_class_t Rx_igraph_lazy_names_class; + +static R_xlen_t Rx_igraph_lazy_names_length(SEXP vec) { + return XLENGTH(VECTOR_ELT(R_altrep_data1(vec), 1)); +} + +static SEXP Rx_igraph_lazy_names_materialize(SEXP vec) { + SEXP data=R_altrep_data2(vec); + if (data != R_NilValue) { + return data; + } + + SEXP d1=R_altrep_data1(vec); + SEXP source=VECTOR_ELT(d1, 0); + SEXP idx=VECTOR_ELT(d1, 1); + R_xlen_t n=XLENGTH(idx); + R_xlen_t nsource=XLENGTH(source); + const int *pidx=INTEGER(idx); + + PROTECT(data=Rf_allocVector(STRSXP, n)); + for (R_xlen_t i=0; i < n; i++) { + int j=pidx[i]; + if (j == NA_INTEGER || j < 1 || j > nsource) { + SET_STRING_ELT(data, i, NA_STRING); + } else { + SET_STRING_ELT(data, i, STRING_ELT(source, j - 1)); + } + } + R_set_altrep_data2(vec, data); + UNPROTECT(1); + return data; +} + +/* DATAPTR_RO / DATAPTR_OR_NULL are used here rather than DATAPTR: the latter + * is non-API as of R 4.5. The materialized cache is a standard read-only name + * vector, so a read-only data pointer is all callers (as.vector(), coercion) + * need. */ +static void *Rx_igraph_lazy_names_dataptr(SEXP vec, Rboolean writeable) { + return (void *) DATAPTR_RO(Rx_igraph_lazy_names_materialize(vec)); +} + +static const void *Rx_igraph_lazy_names_dataptr_or_null(SEXP vec) { + SEXP data=R_altrep_data2(vec); + if (data == R_NilValue) { + return NULL; + } + return DATAPTR_OR_NULL(data); +} + +static SEXP Rx_igraph_lazy_names_elt(SEXP vec, R_xlen_t i) { + return STRING_ELT(Rx_igraph_lazy_names_materialize(vec), i); +} + +/* Subsetting stays lazy: return a fresh lazy-names vector with composed + * indices instead of materializing. Falls back to the default (materialize) + * for index types we do not handle here by returning NULL. */ +static SEXP Rx_igraph_lazy_names_extract_subset(SEXP vec, SEXP indx, SEXP call) { + if (TYPEOF(indx) != INTSXP && TYPEOF(indx) != REALSXP) { + return NULL; + } + + SEXP d1=R_altrep_data1(vec); + SEXP source=VECTOR_ELT(d1, 0); + SEXP idx=VECTOR_ELT(d1, 1); + R_xlen_t leni=XLENGTH(idx); + const int *pidx=INTEGER(idx); + + SEXP indx_int=PROTECT(Rf_coerceVector(indx, INTSXP)); + R_xlen_t n=XLENGTH(indx_int); + const int *pind=INTEGER(indx_int); + + SEXP new_idx=PROTECT(Rf_allocVector(INTSXP, n)); + int *pnew=INTEGER(new_idx); + for (R_xlen_t k=0; k < n; k++) { + int p=pind[k]; + if (p == NA_INTEGER || p < 1 || p > leni) { + pnew[k]=NA_INTEGER; + } else { + pnew[k]=pidx[p - 1]; + } + } + + SEXP d1n=PROTECT(Rf_allocVector(VECSXP, 2)); + SET_VECTOR_ELT(d1n, 0, source); + SET_VECTOR_ELT(d1n, 1, new_idx); + SEXP res=R_new_altrep(Rx_igraph_lazy_names_class, d1n, R_NilValue); + UNPROTECT(3); + return res; +} + +/* Construct a lazy-names vector from a character `source` and a (1-based) + * integer `idx`. Returns R_NilValue when `source` is not usable, so callers + * can fall back to no names. */ +SEXP Rx_igraph_lazy_names(SEXP source, SEXP idx) { + if (TYPEOF(source) != STRSXP) { + return R_NilValue; + } + + SEXP idx_int=PROTECT(Rf_coerceVector(idx, INTSXP)); + SEXP d1=PROTECT(Rf_allocVector(VECSXP, 2)); + SET_VECTOR_ELT(d1, 0, source); + SET_VECTOR_ELT(d1, 1, idx_int); + SEXP res=R_new_altrep(Rx_igraph_lazy_names_class, d1, R_NilValue); + UNPROTECT(2); + return res; +} + +/* Batch constructor for a list of vertex sequences. + * + * Builds the whole `lapply(idx_list, unsafe_create_vs, ...)` result in one C + * pass: for each vertex-ID vector it produces a fresh integer payload, attaches + * a lazy-names ALTREP (when the graph is named), and sets the shared `env` + * weak reference, the `graph` id and the `igraph.vs` class. This keeps the + * per-object R overhead (closure call, `as.integer`, `.Call`, `attributes<-`) + * out of the loop entirely. + * + * idx_list : VECSXP of vertex-ID vectors (integer or double) + * names_src : graph's full vertex-name STRSXP, or NULL for unnamed graphs + * env : the shared weak reference (or env) to set as the "env" attr + * graph_id : graph id (character scalar), or NULL to skip the "graph" attr + */ +SEXP Rx_igraph_vs_list(SEXP idx_list, SEXP names_src, SEXP env, SEXP graph_id) { + R_xlen_t n=XLENGTH(idx_list); + int named=(TYPEOF(names_src) == STRSXP); + SEXP env_sym=Rf_install("env"); + SEXP graph_sym=Rf_install("graph"); + SEXP out=PROTECT(Rf_allocVector(VECSXP, n)); + SEXP cls=PROTECT(Rf_mkString("igraph.vs")); + + for (R_xlen_t i=0; i < n; i++) { + SEXP elt=VECTOR_ELT(idx_list, i); + /* Fresh, unshared integer payload: coerceVector returns its argument + * unchanged when the type already matches, so duplicate in that case to + * avoid mutating a caller-owned vector. */ + SEXP payload=PROTECT(Rf_coerceVector(elt, INTSXP)); + if (payload == elt) { + UNPROTECT(1); + payload=PROTECT(Rf_duplicate(elt)); + } + + if (named) { + SEXP d1=PROTECT(Rf_allocVector(VECSXP, 2)); + SET_VECTOR_ELT(d1, 0, names_src); + SET_VECTOR_ELT(d1, 1, payload); + SEXP nm=PROTECT(R_new_altrep(Rx_igraph_lazy_names_class, d1, R_NilValue)); + Rf_setAttrib(payload, R_NamesSymbol, nm); + UNPROTECT(2); + } + + Rf_setAttrib(payload, env_sym, env); + if (graph_id != R_NilValue) { + Rf_setAttrib(payload, graph_sym, graph_id); + } + Rf_setAttrib(payload, R_ClassSymbol, cls); + + SET_VECTOR_ELT(out, i, payload); + UNPROTECT(1); + } + + UNPROTECT(2); + return out; +} + void Rx_igraph_init_vector_class(DllInfo *dll) { Rx_igraph_altrep_from_class=R_make_altreal_class("igraph_from", "base", dll); Rx_igraph_altrep_to_class=R_make_altreal_class("igraph_to", "base", dll); @@ -2510,6 +2686,13 @@ void Rx_igraph_init_vector_class(DllInfo *dll) { R_set_altrep_Length_method(Rx_igraph_altrep_to_class, Rx_igraph_altrep_length); R_set_altvec_Dataptr_method(Rx_igraph_altrep_to_class, Rx_igraph_altrep_to); + + Rx_igraph_lazy_names_class=R_make_altstring_class("igraph_lazy_names", "igraph", dll); + R_set_altrep_Length_method(Rx_igraph_lazy_names_class, Rx_igraph_lazy_names_length); + R_set_altvec_Dataptr_method(Rx_igraph_lazy_names_class, Rx_igraph_lazy_names_dataptr); + R_set_altvec_Dataptr_or_null_method(Rx_igraph_lazy_names_class, Rx_igraph_lazy_names_dataptr_or_null); + R_set_altvec_Extract_subset_method(Rx_igraph_lazy_names_class, Rx_igraph_lazy_names_extract_subset); + R_set_altstring_Elt_method(Rx_igraph_lazy_names_class, Rx_igraph_lazy_names_elt); } void Rx_igraph_init_handlers(DllInfo *dll) { diff --git a/tools/stimulus/types-RR.yaml b/tools/stimulus/types-RR.yaml index bfb5ca69ed8..3c9ac878978 100644 --- a/tools/stimulus/types-RR.yaml +++ b/tools/stimulus/types-RR.yaml @@ -472,7 +472,7 @@ VERTEXSET_LIST: OUTCONV: OUT: |- if (igraph_opt("return.vs.es")) { - %I% <- lapply(%I%, unsafe_create_vs, graph = %I1%, verts = V(%I1%)) + %I% <- create_vs_list(%I1%, %I%) } EDGESET_LIST: diff --git a/touchstone/script.R b/touchstone/script.R index 1d8dc2eb4a1..d20a3a20993 100644 --- a/touchstone/script.R +++ b/touchstone/script.R @@ -177,5 +177,122 @@ benchmark_run( n = 20 ) +# --------------------------------------------------------------------------- +# Group #5 - vertex/edge sequence construction on named graphs +# Functions that return (many) vertex/edge sequences pay for building the +# `names`/`vnames` attribute and attaching a graph reference to every object. +# These benchmarks exercise that construction path on *named* graphs, where +# the cost is highest. `max_cliques()` is the canonical case: it returns tens +# of thousands of vertex sequences, one per clique. +# --------------------------------------------------------------------------- +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnp(200L, 0.16, directed = FALSE) + V(g)$name <- paste0("v", seq_len(gorder(g))) + }, + max_cliques_named = max_cliques(g), + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(1000L, 5000L) + V(g)$name <- paste0("v", seq_len(1000L)) + es <- E(g) + }, + head_of_named = head_of(g, es), + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(20000L, 50000L) + V(g)$name <- paste0("v", seq_len(20000L)) + }, + V_named = V(g), + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(20000L, 50000L) + V(g)$name <- paste0("v", seq_len(20000L)) + }, + E_named = E(g), + n = 20 +) + +# --------------------------------------------------------------------------- +# Group #6 - what lazy names + batch construction buy +# These illustrate the three distinct wins on named graphs: +# * batch construction of many vertex sequences (one shared graph reference +# and one hoisted name source instead of per-object work); +# * lazy names that are never paid for when only the IDs/sizes are used; +# * O(1) lazy subsetting of a vertex sequence (ALTREP Extract_subset). +# --------------------------------------------------------------------------- + +# ego() returns one vertex sequence per node -- a few thousand sequences built +# in one call. Exercises create_vs_list() through neighborhood(). +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(2000L, 10000L) + V(g)$name <- paste0("v", seq_len(2000L)) + }, + ego_order2_named = ego(g, order = 2, nodes = V(g)), + n = 20 +) + +# Construct many cliques but only read their sizes: with lazy names the name +# vectors are never materialized at all, so this is close to the numeric path. +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnp(200L, 0.16, directed = FALSE) + V(g)$name <- paste0("v", seq_len(gorder(g))) + }, + max_cliques_sizes_named = lengths(max_cliques(g)), + n = 20 +) + +# Enumerate simple paths between hubs on a named graph: another high-volume +# vertex-sequence-list path (create_vs_list via all_simple_paths()). +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(500L, 2500L) + V(g)$name <- paste0("v", seq_len(500L)) + }, + all_simple_paths_named = all_simple_paths(g, 1, 2:6, cutoff = 5), + n = 20 +) + +# Positional subset of a large named vertex sequence. Subsetting a lazy-names +# sequence composes indices in O(1) (ALTREP Extract_subset) instead of copying +# a subset of the name vector. +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(50000L, 100000L) + V(g)$name <- paste0("v", seq_len(50000L)) + v <- V(g) + pick <- sample(50000L, 10000L) + }, + vs_subset_positional = v[pick], + n = 20 +) + # Create the artifacts consumed by the GitHub Action. benchmark_analyze()