diff --git a/R/cycles.R b/R/cycles.R index c3d65b5cae0..25356203038 100644 --- a/R/cycles.R +++ b/R/cycles.R @@ -31,6 +31,7 @@ #' a specific cycle. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param mode Character constant specifying how to handle directed graphs. #' `out` follows edge directions, `in` follows edges in the reverse direction, #' and `all` ignores edge directions. Ignored in undirected graphs. diff --git a/R/decomposition.R b/R/decomposition.R index cdfce737fc1..233ed25673b 100644 --- a/R/decomposition.R +++ b/R/decomposition.R @@ -67,6 +67,7 @@ is.chordal <- function( #' #' @param graph The input graph. It may be directed, but edge directions are #' ignored, as the algorithm is defined for undirected graphs. +#' @inheritParams rlang::args_dots_empty #' @param alpha Numeric vector, the maximal chardinality ordering of the #' vertices. If it is `NULL`, then it is automatically calculated by #' calling [max_cardinality()], or from `alpham1` if @@ -118,11 +119,44 @@ is.chordal <- function( #' is_chordal <- function( graph, + ..., alpha = NULL, alpham1 = NULL, fillin = FALSE, newgraph = FALSE ) { + # BEGIN GENERATED ARG_HANDLE: is_chordal, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + alpha = alpha, + alpham1 = alpham1, + fillin = fillin, + newgraph = newgraph + ), + recover_new = c("alpha", "alpham1", "fillin", "newgraph"), + recover_old = c("alpha", "alpham1", "fillin", "newgraph"), + match_names = c("alpha", "alpham1", "fillin", "newgraph"), + match_to = c("alpha", "alpham1", "fillin", "newgraph"), + defaults = list( + alpha = NULL, + alpham1 = NULL, + fillin = FALSE, + newgraph = FALSE + ), + head_args = c("graph"), + fn_name = "is_chordal" + ) + 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) if (!is.null(alpha)) { alpha <- as.numeric(alpha) - 1 diff --git a/R/trees.R b/R/trees.R index 04634991d35..e97db49d324 100644 --- a/R/trees.R +++ b/R/trees.R @@ -13,6 +13,7 @@ #' not to be a tree. #' #' @param graph An igraph graph object +#' @inheritParams rlang::args_dots_empty #' @param mode Whether to consider edge directions in a directed graph. #' \sQuote{all} ignores edge directions; \sQuote{out} requires edges to be #' oriented outwards from the root, \sQuote{in} requires edges to be oriented @@ -42,9 +43,32 @@ #' @export is_tree <- function( graph, + ..., mode = c("out", "in", "all", "total"), details = FALSE ) { + # BEGIN GENERATED ARG_HANDLE: is_tree, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(mode = mode, details = details), + recover_new = c("mode", "details"), + recover_old = c("mode", "details"), + match_names = c("mode", "details"), + match_to = c("mode", "details"), + defaults = list(mode = c("out", "in", "all", "total"), details = FALSE), + head_args = c("graph"), + fn_name = "is_tree" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + out <- is_tree_impl( graph = graph, mode = mode, @@ -72,6 +96,7 @@ is_tree <- function( #' to be a forest. #' #' @param graph An igraph graph object +#' @inheritParams rlang::args_dots_empty #' @param mode Whether to consider edge directions in a directed graph. #' \sQuote{all} ignores edge directions; \sQuote{out} requires edges to be #' oriented outwards from the root, \sQuote{in} requires edges to be oriented @@ -101,9 +126,32 @@ is_tree <- function( #' @export is_forest <- function( graph, + ..., mode = c("out", "in", "all", "total"), details = FALSE ) { + # BEGIN GENERATED ARG_HANDLE: is_forest, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(mode = mode, details = details), + recover_new = c("mode", "details"), + recover_old = c("mode", "details"), + match_names = c("mode", "details"), + match_to = c("mode", "details"), + defaults = list(mode = c("out", "in", "all", "total"), details = FALSE), + head_args = c("graph"), + fn_name = "is_forest" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + is_forest_impl( graph = graph, mode = mode, @@ -148,6 +196,7 @@ to_prufer <- function(graph) { #' #' @param graph The input graph to sample from. Edge directions are ignored if #' the graph is directed. +#' @inheritParams rlang::args_dots_empty #' @param vid When the graph is disconnected, this argument specifies how to #' handle the situation. When the argument is zero (the default), the sampling #' will be performed component-wise, and the result will be a spanning forest. @@ -167,7 +216,33 @@ to_prufer <- function(graph) { #' #' @family trees #' @export -sample_spanning_tree <- function(graph, vid = 0) { +sample_spanning_tree <- function( + graph, + ..., + vid = 0 +) { + # BEGIN GENERATED ARG_HANDLE: sample_spanning_tree, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(vid = vid), + recover_new = c("vid"), + recover_old = c("vid"), + match_names = c("vid"), + match_to = c("vid"), + defaults = list(vid = 0), + head_args = c("graph"), + fn_name = "sample_spanning_tree" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + random_spanning_tree_impl( graph = graph, vid = vid diff --git a/man/is_chordal.Rd b/man/is_chordal.Rd index e47d548bcab..8b2f878306b 100644 --- a/man/is_chordal.Rd +++ b/man/is_chordal.Rd @@ -6,6 +6,7 @@ \usage{ is_chordal( graph, + ..., alpha = NULL, alpham1 = NULL, fillin = FALSE, @@ -16,6 +17,8 @@ is_chordal( \item{graph}{The input graph. It may be directed, but edge directions are ignored, as the algorithm is defined for undirected graphs.} +\item{...}{These dots are for future extensions and must be empty.} + \item{alpha}{Numeric vector, the maximal chardinality ordering of the vertices. If it is \code{NULL}, then it is automatically calculated by calling \code{\link[=max_cardinality]{max_cardinality()}}, or from \code{alpham1} if diff --git a/man/is_forest.Rd b/man/is_forest.Rd index 094d24e1f3e..2db5a6780a8 100644 --- a/man/is_forest.Rd +++ b/man/is_forest.Rd @@ -4,11 +4,13 @@ \alias{is_forest} \title{Decide whether a graph is a forest.} \usage{ -is_forest(graph, mode = c("out", "in", "all", "total"), details = FALSE) +is_forest(graph, ..., mode = c("out", "in", "all", "total"), details = FALSE) } \arguments{ \item{graph}{An igraph graph object} +\item{...}{These dots are for future extensions and must be empty.} + \item{mode}{Whether to consider edge directions in a directed graph. \sQuote{all} ignores edge directions; \sQuote{out} requires edges to be oriented outwards from the root, \sQuote{in} requires edges to be oriented diff --git a/man/is_tree.Rd b/man/is_tree.Rd index b2bfd0da673..86e89206f24 100644 --- a/man/is_tree.Rd +++ b/man/is_tree.Rd @@ -4,11 +4,13 @@ \alias{is_tree} \title{Decide whether a graph is a tree.} \usage{ -is_tree(graph, mode = c("out", "in", "all", "total"), details = FALSE) +is_tree(graph, ..., mode = c("out", "in", "all", "total"), details = FALSE) } \arguments{ \item{graph}{An igraph graph object} +\item{...}{These dots are for future extensions and must be empty.} + \item{mode}{Whether to consider edge directions in a directed graph. \sQuote{all} ignores edge directions; \sQuote{out} requires edges to be oriented outwards from the root, \sQuote{in} requires edges to be oriented diff --git a/man/sample_spanning_tree.Rd b/man/sample_spanning_tree.Rd index 393730d9502..a6b5665dbb8 100644 --- a/man/sample_spanning_tree.Rd +++ b/man/sample_spanning_tree.Rd @@ -4,12 +4,14 @@ \alias{sample_spanning_tree} \title{Samples from the spanning trees of a graph randomly and uniformly} \usage{ -sample_spanning_tree(graph, vid = 0) +sample_spanning_tree(graph, ..., vid = 0) } \arguments{ \item{graph}{The input graph to sample from. Edge directions are ignored if the graph is directed.} +\item{...}{These dots are for future extensions and must be empty.} + \item{vid}{When the graph is disconnected, this argument specifies how to handle the situation. When the argument is zero (the default), the sampling will be performed component-wise, and the result will be a spanning forest. diff --git a/tests/testthat/test-decomposition.R b/tests/testthat/test-decomposition.R index a425328dba8..a79f095fd0d 100644 --- a/tests/testthat/test-decomposition.R +++ b/tests/testthat/test-decomposition.R @@ -40,3 +40,41 @@ test_that("is_chordal works", { ic2 <- is_chordal(g2, fillin = TRUE) expect_equal(ic2, list(chordal = TRUE, fillin = numeric(), newgraph = NULL)) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +# The first example graph from the Tarjan-Yannakakis paper, as in the test above. +tarjan_yannakakis_graph <- function() { + graph_from_literal( + A - B:C:I, B - A:C:D, C - A:B:E:H, D - B:E:F, + E - C:D:F:H, F - D:E:G, G - F:H, H - C:E:G:I, + I - A:H + ) +} + +test_that("is_chordal accepts alpha, alpham1, fillin, and newgraph by name", { + g <- tarjan_yannakakis_graph() + mc <- max_cardinality(g) + + ic <- is_chordal( + g, + alpha = mc$alpha, + alpham1 = mc$alpham1, + fillin = TRUE, + newgraph = TRUE + ) + expect_false(ic$chordal) + expect_equal(unique(sort(ic$fillin)), c(1, 2, 5, 6, 7, 8)) + # The triangulated graph adds exactly the fill-in edges and is chordal. + expect_vcount(ic$newgraph, vcount(g)) + expect_ecount(ic$newgraph, ecount(g) + length(ic$fillin) / 2) + expect_true(is_chordal(ic$newgraph)$chordal) +}) + +test_that("is_chordal recovers legacy positional arguments", { + g <- tarjan_yannakakis_graph() + mc <- max_cardinality(g) + lifecycle::expect_deprecated(res <- is_chordal(g, mc$alpha)) + expect_identical(res, is_chordal(g, alpha = mc$alpha)) + expect_false(res$chordal) +}) diff --git a/tests/testthat/test-trees.R b/tests/testthat/test-trees.R index caa675ad686..0e20c7bb0e6 100644 --- a/tests/testthat/test-trees.R +++ b/tests/testthat/test-trees.R @@ -227,3 +227,47 @@ test_that("subgraph.edges deprecation", { edges <- sample_spanning_tree(g) expect_snapshot(subgraph.edges(g, edges, delete.vertices = FALSE)) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +test_that("is_tree recovers legacy positional arguments", { + g <- make_tree(7, 2, mode = "in") + # The default mode "out" rejects an in-tree, so the recovered value is visible. + expect_false(is_tree(g)) + lifecycle::expect_deprecated(res <- is_tree(g, "in")) + expect_identical(res, is_tree(g, mode = "in")) + expect_true(res) +}) + +test_that("is_forest returns component roots for the requested mode", { + g <- make_tree(3, mode = "in") + make_tree(5, 3, mode = "in") + + res <- is_forest(g, mode = "in", details = TRUE) + expect_named(res, c("res", "roots")) + expect_true(res$res) + expect_equal(ignore_attr = TRUE, res$roots, V(g)[c(1, 4)]) + + # The same forest fails the check against the opposite orientation. + res_out <- is_forest(g, mode = "out", details = TRUE) + expect_named(res_out, c("res", "roots")) + expect_false(res_out$res) +}) + +test_that("is_forest recovers legacy positional arguments", { + g <- make_graph(c(1, 2, 2, 3, 2, 4, 5, 4), n = 6, directed = TRUE) + # The default mode "out" rejects this graph, so the recovered value is visible. + expect_false(is_forest(g)) + lifecycle::expect_deprecated(res <- is_forest(g, "all")) + expect_identical(res, is_forest(g, mode = "all")) + expect_true(res) +}) + +test_that("sample_spanning_tree recovers legacy positional arguments", { + g <- make_full_graph(8) %du% make_full_graph(5) + lifecycle::expect_deprecated( + res <- igraph_with_seed(42, sample_spanning_tree(g, 9)) + ) + # vid = 9 selects the 5-vertex component, so its spanning tree has 4 edges. + expect_length(res, 4) + expect_equal(res, igraph_with_seed(42, sample_spanning_tree(g, vid = 9))) +}) diff --git a/tools/migrations/trees.R b/tools/migrations/trees.R new file mode 100644 index 00000000000..2c911e5641a --- /dev/null +++ b/tools/migrations/trees.R @@ -0,0 +1,50 @@ +# Argument-signature migrations: trees +# Schema: see tools/migrations.R. Regenerate with: +# Rscript tools/generate-migrations.R + +migrations <- list( + is_chordal = list( + old = function(graph, alpha, alpham1, fillin, newgraph) {}, + new = function( + graph, + ..., + alpha = NULL, + alpham1 = NULL, + fillin = FALSE, + newgraph = FALSE + ) {}, + when = "3.0.0" + ), + + is_forest = list( + old = function(graph, mode, details) {}, + new = function( + graph, + ..., + mode = c("out", "in", "all", "total"), + details = FALSE + ) {}, + when = "3.0.0" + ), + + is_tree = list( + old = function(graph, mode, details) {}, + new = function( + graph, + ..., + mode = c("out", "in", "all", "total"), + details = FALSE + ) {}, + when = "3.0.0" + ), + + sample_spanning_tree = list( + old = function(graph, vid) {}, + new = function( + graph, + ..., + vid = 0 + ) {}, + when = "3.0.0" + ) +)