diff --git a/R/operators.R b/R/operators.R index a040324d0f9..4560514a944 100644 --- a/R/operators.R +++ b/R/operators.R @@ -877,6 +877,7 @@ difference.igraph <- function(big, small, byname = "auto", ...) { #' attributes are lost. #' #' @param graph The input graph, can be directed or undirected. +#' @inheritParams rlang::args_dots_empty #' @param loops Logical, whether to generate loop edges. #' @return A new graph object. #' @author Gabor Csardi \email{csardi.gabor@@gmail.com} @@ -896,7 +897,33 @@ difference.igraph <- function(big, small, byname = "auto", ...) { #' gu #' isomorphic(gu, make_full_graph(vcount(g))) #' -complementer <- function(graph, loops = FALSE) { +complementer <- function( + graph, + ..., + loops = FALSE +) { + # BEGIN GENERATED ARG_HANDLE: complementer, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(loops = loops), + recover_new = c("loops"), + recover_old = c("loops"), + match_names = c("loops"), + match_to = c("loops"), + defaults = list(loops = FALSE), + head_args = c("graph"), + fn_name = "complementer" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) complementer_impl(graph = graph, loops = as.logical(loops)) @@ -948,6 +975,7 @@ complementer <- function(graph, loops = FALSE) { #' @aliases %c% #' @param g1 The first input graph. #' @param g2 The second input graph. +#' @inheritParams rlang::args_dots_empty #' @param byname A Logical, or the character scalar `auto`. Whether #' to perform the operation based on symbolic vertex names. If it is #' `auto`, that means `TRUE` if both graphs are named and @@ -975,11 +1003,64 @@ complementer <- function(graph, loops = FALSE) { compose <- function( g1, g2, + ..., byname = "auto", graph.attr.comb = igraph_opt("graph.attr.comb"), vertex.attr.comb = "rename", edge.attr.comb = "rename" ) { + # BEGIN GENERATED ARG_HANDLE: compose, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + byname = byname, + graph.attr.comb = graph.attr.comb, + vertex.attr.comb = vertex.attr.comb, + edge.attr.comb = edge.attr.comb + ), + recover_new = c( + "byname", + "graph.attr.comb", + "vertex.attr.comb", + "edge.attr.comb" + ), + recover_old = c( + "byname", + "graph.attr.comb", + "vertex.attr.comb", + "edge.attr.comb" + ), + match_names = c( + "byname", + "graph.attr.comb", + "vertex.attr.comb", + "edge.attr.comb" + ), + match_to = c( + "byname", + "graph.attr.comb", + "vertex.attr.comb", + "edge.attr.comb" + ), + defaults = list( + byname = "auto", + graph.attr.comb = igraph_opt("graph.attr.comb"), + vertex.attr.comb = "rename", + edge.attr.comb = "rename" + ), + head_args = c("g1", "g2"), + fn_name = "compose" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(g1) ensure_igraph(g2) diff --git a/R/rewire.R b/R/rewire.R index f054bda7a65..3e612a8b2f8 100644 --- a/R/rewire.R +++ b/R/rewire.R @@ -102,6 +102,7 @@ rewire_keeping_degseq <- function(graph, loops, niter) { #' Note that this method might create graphs with multiple and/or loop edges. #' #' @param prob The rewiring probability, a real number between zero and one. +#' @inheritParams rlang::args_dots_empty #' @param loops Logical, whether loop edges are allowed in the rewired #' graph. #' @param multiple Logical, whether multiple edges are allowed in the @@ -130,10 +131,37 @@ rewire_keeping_degseq <- function(graph, loops, niter) { #' degree(g, mode = "in") == degree(g2, mode = "in") each_edge <- function( prob, + ..., loops = FALSE, multiple = FALSE, mode = c("all", "out", "in", "total") ) { + # BEGIN GENERATED ARG_HANDLE: each_edge, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(loops = loops, multiple = multiple, mode = mode), + recover_new = c("loops", "multiple", "mode"), + recover_old = c("loops", "multiple", "mode"), + match_names = c("loops", "multiple", "mode"), + match_to = c("loops", "multiple", "mode"), + defaults = list( + loops = FALSE, + multiple = FALSE, + mode = c("all", "out", "in", "total") + ), + head_args = c("prob"), + fn_name = "each_edge" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + mode <- igraph_match_arg(mode) multiple <- as.logical(multiple) if (mode != "all" && mode != "total") { diff --git a/man/complementer.Rd b/man/complementer.Rd index e296947d887..5e38be4b36a 100644 --- a/man/complementer.Rd +++ b/man/complementer.Rd @@ -4,11 +4,13 @@ \alias{complementer} \title{Complementer of a graph} \usage{ -complementer(graph, loops = FALSE) +complementer(graph, ..., loops = FALSE) } \arguments{ \item{graph}{The input graph, can be directed or undirected.} +\item{...}{These dots are for future extensions and must be empty.} + \item{loops}{Logical, whether to generate loop edges.} } \value{ diff --git a/man/compose.Rd b/man/compose.Rd index 72aeb11772f..ea67a1050ca 100644 --- a/man/compose.Rd +++ b/man/compose.Rd @@ -8,6 +8,7 @@ compose( g1, g2, + ..., byname = "auto", graph.attr.comb = igraph_opt("graph.attr.comb"), vertex.attr.comb = "rename", @@ -19,6 +20,8 @@ compose( \item{g2}{The second input graph.} +\item{...}{These dots are for future extensions and must be empty.} + \item{byname}{A Logical, or the character scalar \code{auto}. Whether to perform the operation based on symbolic vertex names. If it is \code{auto}, that means \code{TRUE} if both graphs are named and diff --git a/man/each_edge.Rd b/man/each_edge.Rd index 65bfc40e78a..4b1a3bcc7d1 100644 --- a/man/each_edge.Rd +++ b/man/each_edge.Rd @@ -6,6 +6,7 @@ \usage{ each_edge( prob, + ..., loops = FALSE, multiple = FALSE, mode = c("all", "out", "in", "total") @@ -14,6 +15,8 @@ each_edge( \arguments{ \item{prob}{The rewiring probability, a real number between zero and one.} +\item{...}{These dots are for future extensions and must be empty.} + \item{loops}{Logical, whether loop edges are allowed in the rewired graph.} diff --git a/tests/testthat/test-operators.R b/tests/testthat/test-operators.R index 715d56fe596..b79abf500b9 100644 --- a/tests/testthat/test-operators.R +++ b/tests/testthat/test-operators.R @@ -1413,3 +1413,60 @@ test_that("simplify() rejects 'rename' combiner", { "rename" ) }) + +# ---- ellipsis migration: argument coverage ---------------------------------- + +test_that("complementer() takes `loops` by name and recovers it positionally", { + g <- make_ring(4) + + # The complement of C4 has the two missing cross edges plus one loop per vertex. + gc <- complementer(g, loops = TRUE) + expect_ecount(gc, 6) + expect_equal(sum(which_loop(gc)), 4) + + rlang::local_options(lifecycle_verbosity = "warning") + lifecycle::expect_deprecated( + res <- complementer(g, TRUE) + ) + expect_identical_graphs(res, complementer(g, loops = TRUE)) +}) + +test_that("compose() takes all tail arguments by name", { + g1 <- graph_from_literal(A -+ B, B -+ C) + g2 <- graph_from_literal(D -+ E, E -+ F) + g1$kind <- "one" + g2$kind <- "two" + V(g1)$score <- 1:3 + V(g2)$score <- c(10, 20, 30) + E(g1)$w <- c(1, 2) + E(g2)$w <- c(10, 20) + + res <- compose( + g1, + g2, + byname = FALSE, + graph.attr.comb = "first", + vertex.attr.comb = "first", + edge.attr.comb = "concat" + ) + + # By vertex ID the graphs overlap; by name they are disjoint (6 vertices, no edge). + expect_vcount(res, 3) + expect_ecount(res, 1) + expect_equal(as_edgelist(res), cbind("A", "C")) + # "first" keeps the first graph's attribute, "concat" concatenates both edges'. + expect_equal(res$kind, "one") + expect_equal(V(res)$score, 1:3) + expect_equal(E(res)$w, list(c(1, 20))) +}) + +test_that("compose() recovers legacy positional arguments", { + g1 <- graph_from_literal(A -+ B, B -+ C) + g2 <- graph_from_literal(D -+ E, E -+ F) + + rlang::local_options(lifecycle_verbosity = "warning") + lifecycle::expect_deprecated( + res <- compose(g1, g2, FALSE) + ) + expect_identical_graphs(res, compose(g1, g2, byname = FALSE)) +}) diff --git a/tests/testthat/test-rewire.R b/tests/testthat/test-rewire.R index 37b0f08e80b..4382025e8ce 100644 --- a/tests/testthat/test-rewire.R +++ b/tests/testthat/test-rewire.R @@ -22,3 +22,33 @@ test_that("rewire() with zero probability does not do anything", { g2 <- g %>% rewire(each_edge(prob = 0)) expect_identical_graphs(g, g2) }) + +# ---- ellipsis migration: argument coverage ---------------------------------- + +test_that("each_edge() takes all tail arguments by name", { + igraph_local_seed(42) + g <- sample_pa(100) + + g2 <- g %>% + rewire(each_edge(prob = 1, loops = TRUE, multiple = TRUE, mode = "out")) + # Rewiring only the heads keeps the out-degree sequence and the edge count. + expect_equal(degree(g2, mode = "out"), degree(g, mode = "out")) + expect_ecount(g2, ecount(g)) + # With this seed the rewiring uses the freedom that `loops = TRUE` grants. + expect_true(any(which_loop(g2))) + + # On a single-vertex graph the rewired edge can only stay a loop. + g_loop <- make_graph(c(1, 1)) + expect_identical_graphs( + rewire(g_loop, each_edge(prob = 1, loops = TRUE, multiple = TRUE)), + g_loop + ) +}) + +test_that("each_edge() recovers legacy positional arguments", { + rlang::local_options(lifecycle_verbosity = "warning") + lifecycle::expect_deprecated( + res <- each_edge(0.3, TRUE) + ) + expect_identical(res, each_edge(0.3, loops = TRUE)) +}) diff --git a/tools/migrations/operators.R b/tools/migrations/operators.R new file mode 100644 index 00000000000..219b04a5187 --- /dev/null +++ b/tools/migrations/operators.R @@ -0,0 +1,48 @@ +# Argument-signature migrations: operators +# Schema: see tools/migrations/README.md. Regenerate with: +# Rscript tools/generate-migrations.R + +migrations <- list( + complementer = list( + old = function(graph, loops) {}, + new = function( + graph, + ..., + loops = FALSE + ) {}, + when = "3.0.0" + ), + + compose = list( + old = function( + g1, + g2, + byname, + graph.attr.comb, + vertex.attr.comb, + edge.attr.comb + ) {}, + new = function( + g1, + g2, + ..., + byname = "auto", + graph.attr.comb = igraph_opt("graph.attr.comb"), + vertex.attr.comb = "rename", + edge.attr.comb = "rename" + ) {}, + when = "3.0.0" + ), + + each_edge = list( + old = function(prob, loops, multiple, mode) {}, + new = function( + prob, + ..., + loops = FALSE, + multiple = FALSE, + mode = c("all", "out", "in", "total") + ) {}, + when = "3.0.0" + ) +)