From 15b57820b401bf86cc061c2d79499fb63dae503f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 09:55:50 +0000 Subject: [PATCH 1/6] fix: Raise the weighted `cluster_edge_betweenness()` warnings from R The C core no longer warns about weighted edge betweenness runs, so the glue re-added the igraph 0.10 warnings with `IGRAPH_WARNING()`, which reported the glue file and line as their source. `cluster_edge_betweenness()` now raises them, the first one only, as igraph 0.10 did through the warning handler. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- R/community.R | 14 ++++++++++++++ src/rinterface_extra.c | 14 ++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/R/community.R b/R/community.R index 33dad0f2777..e0a8b015568 100644 --- a/R/community.R +++ b/R/community.R @@ -2283,6 +2283,20 @@ cluster_edge_betweenness <- function( as.logical(modularity), as.logical(membership) ) + # igraph 0.10 warned about weighted runs in the C core, which no longer does; + # only the first of its two warnings reached R. + if (!is.null(weights)) { + if (isTRUE(as.logical(membership))) { + cli::cli_warn( + "Membership vector will be selected based on the highest modularity score." + ) + } else if (isTRUE(as.logical(modularity))) { + cli::cli_warn(paste( + "Modularity calculation with weighted edge betweenness community detection might not make sense --", + "modularity treats edge weights as similarities while edge betweenness treats them as distances." + )) + } + } if (igraph_opt("add.vertex.names") && is_named(graph)) { res$names <- V(graph)$name } diff --git a/src/rinterface_extra.c b/src/rinterface_extra.c index 303844cd0b9..dfc199e37a0 100644 --- a/src/rinterface_extra.c +++ b/src/rinterface_extra.c @@ -6001,20 +6001,10 @@ SEXP Rx_igraph_community_edge_betweenness(SEXP graph, SEXP pweights, * weights as path lengths for the betweenness, and to weigh the modularity * that picks the membership. So the edges are removed by lengths, and the * merges, modularity and membership come from - * igraph_community_eb_get_merges() with the weights, as igraph 0.10 did, - * with the warnings it gave. */ + * igraph_community_eb_get_merges() with the weights, as igraph 0.10 did. + * The warnings igraph 0.10 gave here are raised by cluster_edge_betweenness(). */ if (ppweights) { IGRAPH_R_CHECK(igraph_community_edge_betweenness(&g, &res, ppeb, 0, 0, 0, 0, directed, /* weights = */ 0, /* lengths = */ ppweights)); - if (ppmembership) { - IGRAPH_WARNING("Membership vector will be selected based on the highest " - "modularity score."); - } - if (ppmodularity || ppmembership) { - IGRAPH_WARNING("Modularity calculation with weighted edge betweenness " - "community detection might not make sense -- modularity treats edge " - "weights as similarities while edge betwenness treats them as " - "distances."); - } if (ppmerges || ppbridges || ppmodularity || ppmembership) { IGRAPH_R_CHECK(igraph_community_eb_get_merges(&g, directed, &res, ppweights, ppmerges, ppbridges, ppmodularity, ppmembership)); } From 8cb09116bb246ce43b62979b40df45509215e770 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 09:56:48 +0000 Subject: [PATCH 2/6] chore: Drop the `_dijkstra_impl()` aliases Upstream merged the weighted `_dijkstra` variants into the base functions, and nothing in the package calls the six aliases kept for their names. The generated-interface tests that called four of them now call the base functions, and the one duplicating `pseudo_diameter_impl()` is gone. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- R/paths.R | 10 ---- tests/testthat/_snaps/aaa-auto.md | 69 ++++++--------------------- tests/testthat/test-aaa-auto.R | 79 ++++++++----------------------- 3 files changed, 36 insertions(+), 122 deletions(-) diff --git a/R/paths.R b/R/paths.R index b994d791c2e..92650d5944c 100644 --- a/R/paths.R +++ b/R/paths.R @@ -450,16 +450,6 @@ graph_center <- function( ) } -# Upstream merged the weighted `_dijkstra` variants into the base functions, -# which take their weights argument (krlmlr/igraph@d8febb1): the -# `_dijkstra_impl()` names stay, as aliases with the same arguments. -average_path_length_dijkstra_impl <- average_path_length_impl -diameter_dijkstra_impl <- diameter_impl -eccentricity_dijkstra_impl <- eccentricity_impl -graph_center_dijkstra_impl <- graph_center_impl -pseudo_diameter_dijkstra_impl <- pseudo_diameter_impl -radius_dijkstra_impl <- radius_impl - #' @rdname distances #' @inheritParams rlang::args_dots_empty #' @param directed Whether to consider directed paths in directed graphs, diff --git a/tests/testthat/_snaps/aaa-auto.md b/tests/testthat/_snaps/aaa-auto.md index 89ee4cde420..c251ea20cab 100644 --- a/tests/testthat/_snaps/aaa-auto.md +++ b/tests/testthat/_snaps/aaa-auto.md @@ -3689,32 +3689,32 @@ Error in `ensure_igraph()`: ! Must provide a graph object (provided `NULL`). -# eccentricity_dijkstra_impl basic +# eccentricity_impl modes Code - eccentricity_dijkstra_impl(graph = g) + eccentricity_impl(graph = g) Output [1] 2 1 2 --- Code - eccentricity_dijkstra_impl(graph = g, mode = "in") + eccentricity_impl(graph = g, mode = "in") Output [1] 2 1 2 -# eccentricity_dijkstra_impl errors +# eccentricity_impl errors Code - eccentricity_dijkstra_impl(graph = NULL) + eccentricity_impl(graph = NULL) Condition Error in `ensure_igraph()`: ! Must provide a graph object (provided `NULL`). -# graph_center_dijkstra_impl basic +# graph_center_impl modes Code - graph_center_dijkstra_impl(graph = g) + graph_center_impl(graph = g) Output + 1/3 vertex: [1] 2 @@ -3722,37 +3722,37 @@ --- Code - graph_center_dijkstra_impl(graph = g, mode = "in") + graph_center_impl(graph = g, mode = "in") Output + 1/3 vertex: [1] 2 -# graph_center_dijkstra_impl errors +# graph_center_impl errors Code - graph_center_dijkstra_impl(graph = NULL) + graph_center_impl(graph = NULL) Condition Error in `ensure_igraph()`: ! Must provide a graph object (provided `NULL`). -# radius_dijkstra_impl basic +# radius_impl modes Code - radius_dijkstra_impl(graph = g) + radius_impl(graph = g) Output [1] 1 --- Code - radius_dijkstra_impl(graph = g, mode = "in") + radius_impl(graph = g, mode = "in") Output [1] 1 -# radius_dijkstra_impl errors +# radius_impl errors Code - radius_dijkstra_impl(graph = NULL) + radius_impl(graph = NULL) Condition Error in `ensure_igraph()`: ! Must provide a graph object (provided `NULL`). @@ -3795,45 +3795,6 @@ Error in `ensure_igraph()`: ! Must provide a graph object (provided `NULL`). -# pseudo_diameter_dijkstra_impl basic - - Code - pseudo_diameter_dijkstra_impl(graph = g, start_vid = 1) - Output - $diameter - [1] 2 - - $from - [1] 0 - - $to - [1] 2 - - ---- - - Code - pseudo_diameter_dijkstra_impl(graph = g, start_vid = 1, directed = FALSE, - unconnected = FALSE) - Output - $diameter - [1] 2 - - $from - [1] 0 - - $to - [1] 2 - - -# pseudo_diameter_dijkstra_impl errors - - Code - pseudo_diameter_dijkstra_impl(graph = NULL, start_vid = 1) - Condition - Error in `ensure_igraph()`: - ! Must provide a graph object (provided `NULL`). - # diversity_impl basic Code diff --git a/tests/testthat/test-aaa-auto.R b/tests/testthat/test-aaa-auto.R index 34095875e31..96803516c03 100644 --- a/tests/testthat/test-aaa-auto.R +++ b/tests/testthat/test-aaa-auto.R @@ -3827,92 +3827,92 @@ test_that("contract_vertices_impl errors", { )) }) -# 150. eccentricity_dijkstra_impl +# 150. eccentricity_impl -test_that("eccentricity_dijkstra_impl basic", { +test_that("eccentricity_impl modes", { igraph_local_seed(20250909) g <- path_graph_impl( n = 3 ) - expect_snapshot(eccentricity_dijkstra_impl( + expect_snapshot(eccentricity_impl( graph = g )) - expect_snapshot(eccentricity_dijkstra_impl( + expect_snapshot(eccentricity_impl( graph = g, mode = "in" )) # Structured tests - result <- eccentricity_dijkstra_impl( + result <- eccentricity_impl( graph = g ) expect_true(is.numeric(result)) }) -test_that("eccentricity_dijkstra_impl errors", { +test_that("eccentricity_impl errors", { igraph_local_seed(20250909) - expect_snapshot_igraph_error(eccentricity_dijkstra_impl( + expect_snapshot_igraph_error(eccentricity_impl( graph = NULL )) }) -# 151. graph_center_dijkstra_impl +# 151. graph_center_impl -test_that("graph_center_dijkstra_impl basic", { +test_that("graph_center_impl modes", { igraph_local_seed(20250909) g <- path_graph_impl( n = 3 ) - expect_snapshot(graph_center_dijkstra_impl( + expect_snapshot(graph_center_impl( graph = g )) - expect_snapshot(graph_center_dijkstra_impl( + expect_snapshot(graph_center_impl( graph = g, mode = "in" )) # Structured tests - result <- graph_center_dijkstra_impl( + result <- graph_center_impl( graph = g ) expect_s3_class(result, "igraph.vs") }) -test_that("graph_center_dijkstra_impl errors", { +test_that("graph_center_impl errors", { igraph_local_seed(20250909) - expect_snapshot_igraph_error(graph_center_dijkstra_impl( + expect_snapshot_igraph_error(graph_center_impl( graph = NULL )) }) -# 152. radius_dijkstra_impl +# 152. radius_impl -test_that("radius_dijkstra_impl basic", { +test_that("radius_impl modes", { igraph_local_seed(20250909) g <- path_graph_impl( n = 3 ) - expect_snapshot(radius_dijkstra_impl( + expect_snapshot(radius_impl( graph = g )) - expect_snapshot(radius_dijkstra_impl( + expect_snapshot(radius_impl( graph = g, mode = "in" )) # Structured tests - result <- radius_dijkstra_impl( + result <- radius_impl( graph = g ) expect_true(is.numeric(result)) }) -test_that("radius_dijkstra_impl errors", { +test_that("radius_impl errors", { igraph_local_seed(20250909) - expect_snapshot_igraph_error(radius_dijkstra_impl( + expect_snapshot_igraph_error(radius_impl( graph = NULL )) }) @@ -3952,43 +3952,6 @@ test_that("pseudo_diameter_impl errors", { )) }) -# 154. pseudo_diameter_dijkstra_impl - -test_that("pseudo_diameter_dijkstra_impl basic", { - igraph_local_seed(20250909) - g <- path_graph_impl( - n = 3 - ) - - expect_snapshot(pseudo_diameter_dijkstra_impl( - graph = g, - start_vid = 1 - )) - expect_snapshot(pseudo_diameter_dijkstra_impl( - graph = g, - start_vid = 1, - directed = FALSE, - unconnected = FALSE - )) - - # Structured tests - result <- pseudo_diameter_dijkstra_impl( - graph = g, - start_vid = 1 - ) - expect_type(result, "list") -}) - -test_that("pseudo_diameter_dijkstra_impl errors", { - igraph_local_seed(20250909) - expect_snapshot_igraph_error( - pseudo_diameter_dijkstra_impl( - graph = NULL, - start_vid = 1 - ) - ) -}) - # 155. diversity_impl test_that("diversity_impl basic", { From 800b5b40f14185fa3a6b9cbb2490f1248b25064b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 09:57:34 +0000 Subject: [PATCH 3/6] chore: Drop the stale `igraph_rewire` Stimulus default `igraph_rewire()` no longer has a `mode` parameter, so its R default in `functions-R.yaml` named nothing. Regenerating the interface leaves its output unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- tools/stimulus/functions-R.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/stimulus/functions-R.yaml b/tools/stimulus/functions-R.yaml index d0a78940ab7..c9bd6312686 100644 --- a/tools/stimulus/functions-R.yaml +++ b/tools/stimulus/functions-R.yaml @@ -301,11 +301,6 @@ igraph_personalized_pagerank_vs: DEPS: vids ON graph, weights ON graph, vector ON graph vids, options ON algo, reset_vids ON graph -igraph_rewire: - R: - DEFAULTS: - mode: 0L - igraph_average_path_length: PARAM_NAMES: unconn_pairs: unconnected From f58160f5450d2a6d6c97cfdb9331561b7f9ba528 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 09:57:43 +0000 Subject: [PATCH 4/6] chore: Drop the redundant `igraph_personalized_pagerank_vs` DEPS override Upstream's `functions.yaml` declares the same dependencies, so the override in `functions-R.yaml` changes nothing. Regenerating the interface leaves its output unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- tools/stimulus/functions-R.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/stimulus/functions-R.yaml b/tools/stimulus/functions-R.yaml index c9bd6312686..f70aa6ca4a3 100644 --- a/tools/stimulus/functions-R.yaml +++ b/tools/stimulus/functions-R.yaml @@ -297,10 +297,6 @@ igraph_personalized_pagerank: PARAM_NAMES: reset: personalized -igraph_personalized_pagerank_vs: - DEPS: vids ON graph, weights ON graph, vector ON graph vids, - options ON algo, reset_vids ON graph - igraph_average_path_length: PARAM_NAMES: unconn_pairs: unconnected From b28054ecd31410032d6ce689f4b38028942ee8a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 10:00:08 +0000 Subject: [PATCH 5/6] docs: Mark `hits_scores(scale)` as superseded `hits_scores()` signals `scale = FALSE` as superseded, because the C core now always scales the largest scores to one, but its documentation still described `scale` as a plain argument. It now carries the superseded badge and says what `FALSE` still does. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- R/centrality.R | 7 ++++--- man/hits_scores.Rd | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/centrality.R b/R/centrality.R index 8c0a855d026..a8ff9ae41be 100644 --- a/R/centrality.R +++ b/R/centrality.R @@ -1716,9 +1716,10 @@ diversity <- function( #' scores are the same as authority scores. #' #' @param graph The input graph. -#' @param scale Logical, whether to scale the result to have a maximum -#' score of one. If no scaling is used then the result vector has unit length -#' in the Euclidean norm. +#' @param scale `r lifecycle::badge("superseded")` Logical, whether to scale +#' the result to have a maximum score of one, which the C core now always does. +#' With `FALSE`, the result vector is rescaled to unit length in the Euclidean norm, +#' except for graphs without (nonzero) edges, where all scores stay one. #' @param weights Optional positive weight vector for calculating weighted #' scores. If the graph has a `weight` edge attribute, then this is used #' by default. Pass `NA` to ignore the weight attribute. This function diff --git a/man/hits_scores.Rd b/man/hits_scores.Rd index ee1440b68c5..56a8c681300 100644 --- a/man/hits_scores.Rd +++ b/man/hits_scores.Rd @@ -11,9 +11,10 @@ hits_scores(graph, ..., scale = TRUE, weights = NULL, options = NULL) \item{...}{These dots are for future extensions and must be empty.} -\item{scale}{Logical, whether to scale the result to have a maximum -score of one. If no scaling is used then the result vector has unit length -in the Euclidean norm.} +\item{scale}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Logical, whether to scale +the result to have a maximum score of one, which the C core now always does. +With \code{FALSE}, the result vector is rescaled to unit length in the Euclidean norm, +except for graphs without (nonzero) edges, where all scores stay one.} \item{weights}{Optional positive weight vector for calculating weighted scores. If the graph has a \code{weight} edge attribute, then this is used From dcfe7b39b4088b5006f47ab9a61f0f56155cd014 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 10:01:43 +0000 Subject: [PATCH 6/6] test: Expect the near-zero warnings in the `eigen_centrality()` stress test Most random graphs in the stress test are disconnected, so `eigen_centrality()` warns that some centralities are nearly zero, which emitted hundreds of warnings on a passing test. The test now checks that only this warning occurs and muffles it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L5n6BLiYoPmk3NFiYDSKZZ --- tests/testthat/test-centrality.R | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-centrality.R b/tests/testthat/test-centrality.R index 3e36af6a327..2f763829093 100644 --- a/tests/testthat/test-centrality.R +++ b/tests/testthat/test-centrality.R @@ -594,9 +594,20 @@ test_that("eigen_centrality() works", { expect_ev(M, v, lambda, eps) } + # Most of these graphs are disconnected, and warn that some centralities + # are nearly zero; no other warning is expected. for (i in 1:1000) { G <- sample_gnm(10, sample(1:20, 1)) - ev <- eigen_centrality(G) + ev <- withCallingHandlers( + eigen_centrality(G), + warning = function(w) { + expect_match( + conditionMessage(w), + "eigenvector centralities are nearly zero" + ) + invokeRestart("muffleWarning") + } + ) expect_good( as_adjacency_matrix(G, sparse = FALSE), ev$vector,