diff --git a/R/cliques.R b/R/cliques.R index 9005ce8f8cc..bb4425f6e4b 100644 --- a/R/cliques.R +++ b/R/cliques.R @@ -381,8 +381,37 @@ max_cliques <- function( } #' @rdname cliques +#' @inheritParams rlang::args_dots_empty #' @export -count_max_cliques <- function(graph, min = NULL, max = NULL, subset = NULL) { +count_max_cliques <- function( + graph, + min = NULL, + max = NULL, + ..., + subset = NULL +) { + # BEGIN GENERATED ARG_HANDLE: count_max_cliques, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(subset = subset), + recover_new = c("subset"), + recover_old = c("subset"), + match_names = c("subset"), + match_to = c("subset"), + defaults = list(subset = NULL), + head_args = c("graph", "min", "max"), + fn_name = "count_max_cliques" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Argument checks ensure_igraph(graph) @@ -437,6 +466,7 @@ clique_num <- function(graph) { #' `NULL` means no limit, i.e. it is the same as 0. #' @param max.weight Numeric constant, upper limit on the weight of the cliques to find. #' `NULL` means no limit. +#' @inheritParams rlang::args_dots_empty #' @param vertex.weights Vertex weight vector. If the graph has a `weight` #' vertex attribute, then this is used by default. If the graph does not have a #' `weight` vertex attribute and this argument is `NULL`, then every @@ -466,11 +496,44 @@ clique_num <- function(graph) { #' weighted_clique_num(g) weighted_cliques <- function( graph, + ..., vertex.weights = NULL, min.weight = 0, max.weight = 0, maximal = FALSE ) { + # BEGIN GENERATED ARG_HANDLE: weighted_cliques, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + vertex.weights = vertex.weights, + min.weight = min.weight, + max.weight = max.weight, + maximal = maximal + ), + recover_new = c("vertex.weights", "min.weight", "max.weight", "maximal"), + recover_old = c("vertex.weights", "min.weight", "max.weight", "maximal"), + match_names = c("vertex.weights", "min.weight", "max.weight", "maximal"), + match_to = c("vertex.weights", "min.weight", "max.weight", "maximal"), + defaults = list( + vertex.weights = NULL, + min.weight = 0, + max.weight = 0, + maximal = FALSE + ), + head_args = c("graph"), + fn_name = "weighted_cliques" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + weighted_cliques_impl( graph = graph, vertex_weights = vertex.weights, @@ -479,17 +542,71 @@ weighted_cliques <- function( maximal = maximal ) } +#' @inheritParams rlang::args_dots_empty #' @export #' @rdname cliques -largest_weighted_cliques <- function(graph, vertex.weights = NULL) { +largest_weighted_cliques <- function( + graph, + ..., + vertex.weights = NULL +) { + # BEGIN GENERATED ARG_HANDLE: largest_weighted_cliques, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(vertex.weights = vertex.weights), + recover_new = c("vertex.weights"), + recover_old = c("vertex.weights"), + match_names = c("vertex.weights"), + match_to = c("vertex.weights"), + defaults = list(vertex.weights = NULL), + head_args = c("graph"), + fn_name = "largest_weighted_cliques" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + largest_weighted_cliques_impl( graph = graph, vertex_weights = vertex.weights ) } +#' @inheritParams rlang::args_dots_empty #' @export #' @rdname cliques -weighted_clique_num <- function(graph, vertex.weights = NULL) { +weighted_clique_num <- function( + graph, + ..., + vertex.weights = NULL +) { + # BEGIN GENERATED ARG_HANDLE: weighted_clique_num, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(vertex.weights = vertex.weights), + recover_new = c("vertex.weights"), + recover_old = c("vertex.weights"), + match_names = c("vertex.weights"), + match_to = c("vertex.weights"), + defaults = list(vertex.weights = NULL), + head_args = c("graph"), + fn_name = "weighted_clique_num" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + weighted_clique_number_impl( graph = graph, vertex_weights = vertex.weights @@ -632,8 +749,37 @@ ivs_size <- function(graph) { independence_number <- ivs_size #' @rdname cliques +#' @inheritParams rlang::args_dots_empty #' @export -clique_size_counts <- function(graph, min = 0, max = 0, maximal = FALSE) { +clique_size_counts <- function( + graph, + ..., + min = 0, + max = 0, + maximal = FALSE +) { + # BEGIN GENERATED ARG_HANDLE: clique_size_counts, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(min = min, max = max, maximal = maximal), + recover_new = c("min", "max", "maximal"), + recover_old = c("min", "max", "maximal"), + match_names = c("min", "max", "maximal"), + match_to = c("min", "max", "maximal"), + defaults = list(min = 0, max = 0, maximal = FALSE), + head_args = c("graph"), + fn_name = "clique_size_counts" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + if (maximal) { maximal_cliques_hist_impl( graph = graph, @@ -683,12 +829,40 @@ is_complete <- function(graph) { #' #' @param graph The input graph. #' @param candidate The vertex set to test for being a clique. +#' @inheritParams rlang::args_dots_empty #' @param directed Whether to consider edge directions. #' @return `is_clique()` returns `TRUE` if the candidate vertex set forms #' a clique. #' @keywords graphs #' @export -is_clique <- function(graph, candidate, directed = FALSE) { +is_clique <- function( + graph, + candidate, + ..., + directed = FALSE +) { + # BEGIN GENERATED ARG_HANDLE: is_clique, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(directed = directed), + recover_new = c("directed"), + recover_old = c("directed"), + match_names = c("directed"), + match_to = c("directed"), + defaults = list(directed = FALSE), + head_args = c("graph", "candidate"), + fn_name = "is_clique" + ) + 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_clique_impl( graph = graph, candidate = candidate, 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/man/cliques.Rd b/man/cliques.Rd index a9d7497d016..61c8ea492c2 100644 --- a/man/cliques.Rd +++ b/man/cliques.Rd @@ -26,17 +26,17 @@ max_cliques( callback = NULL ) -count_max_cliques(graph, min = NULL, max = NULL, subset = NULL) +count_max_cliques(graph, min = NULL, max = NULL, ..., subset = NULL) clique_num(graph) -largest_weighted_cliques(graph, vertex.weights = NULL) +largest_weighted_cliques(graph, ..., vertex.weights = NULL) -weighted_clique_num(graph, vertex.weights = NULL) +weighted_clique_num(graph, ..., vertex.weights = NULL) -clique_size_counts(graph, min = 0, max = 0, maximal = FALSE) +clique_size_counts(graph, ..., min = 0, max = 0, maximal = FALSE) -is_clique(graph, candidate, directed = FALSE) +is_clique(graph, candidate, ..., directed = FALSE) } \arguments{ \item{graph}{The input graph.} diff --git a/man/weighted_cliques.Rd b/man/weighted_cliques.Rd index 4b391c0adef..b9f03e0bd94 100644 --- a/man/weighted_cliques.Rd +++ b/man/weighted_cliques.Rd @@ -6,6 +6,7 @@ \usage{ weighted_cliques( graph, + ..., vertex.weights = NULL, min.weight = 0, max.weight = 0, @@ -16,6 +17,8 @@ weighted_cliques( \item{graph}{The input graph, directed graphs will be considered as undirected ones, multiple edges and loops are ignored.} +\item{...}{These dots are for future extensions and must be empty.} + \item{vertex.weights}{Vertex weight vector. If the graph has a \code{weight} vertex attribute, then this is used by default. If the graph does not have a \code{weight} vertex attribute and this argument is \code{NULL}, then every diff --git a/tests/testthat/test-cliques.R b/tests/testthat/test-cliques.R index 3d481f1f57d..9d14e3d84a2 100644 --- a/tests/testthat/test-cliques.R +++ b/tests/testthat/test-cliques.R @@ -459,3 +459,127 @@ test_that("max_cliques handles errors in callback", { "Error in R callback function" ) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +# Shared fixture: a triangle plus a disjoint edge. +# The `weight` vertex attribute favors the edge, +# an explicit weight vector can flip the result to the triangle. +make_weighted_clique_graph <- function() { + g <- make_full_graph(3) %du% make_full_graph(2) + V(g)$weight <- c(1, 1, 1, 4, 4) + g +} + +test_that("clique_size_counts() recovers legacy positional arguments", { + # `min`, `max` and `maximal` are exercised by name above. + g <- make_full_graph(5) %du% make_full_graph(3) + lifecycle::expect_deprecated( + res <- clique_size_counts(g, 3) + ) + expect_identical(res, clique_size_counts(g, min = 3)) + expect_equal(res, c(0, 0, 11, 5, 1)) +}) + +test_that("count_max_cliques() covers max and subset", { + g <- make_full_graph(5) %du% make_full_graph(3) + # Only the triangle {6, 7, 8} falls within the size bounds. + expect_equal(count_max_cliques(g, min = 2, max = 4), 1) + # `subset` restricts the starting vertices of the search, + # so the counts over a vertex partition sum to the full count. + expect_equal( + count_max_cliques(g, min = 2, max = 4, subset = 1:5) + + count_max_cliques(g, min = 2, max = 4, subset = 6:8), + 1 + ) + + # Legacy positional `subset` is recovered with a deprecation warning. + lifecycle::expect_deprecated( + res <- count_max_cliques(g, 2, 4, 1:5) + ) + expect_identical(res, count_max_cliques(g, min = 2, max = 4, subset = 1:5)) +}) + +test_that("is_clique() covers directed", { + # A directed triangle with a single direction per vertex pair. + g <- make_graph(c("A", "B", "B", "C", "A", "C"), directed = TRUE) + # Ignoring edge directions the three vertices form a clique. + expect_true(is_clique(g, c("A", "B", "C"), directed = FALSE)) + # With directions considered, the reciprocal edges are missing. + expect_false(is_clique(g, c("A", "B", "C"), directed = TRUE)) + + # Legacy positional `directed` is recovered with a deprecation warning. + lifecycle::expect_deprecated( + res <- is_clique(g, c("A", "B", "C"), TRUE) + ) + expect_identical(res, is_clique(g, c("A", "B", "C"), directed = TRUE)) +}) + +test_that("weighted_cliques() covers all tail arguments", { + g <- make_weighted_clique_graph() + w <- c(3, 3, 3, 1, 1) + + # Under the attribute weights only the edge satisfies the weight bounds. + res_attr <- weighted_cliques( + g, + min.weight = 6, + max.weight = 9, + maximal = TRUE + ) + expect_identical(lapply(res_attr, as.numeric), list(c(4, 5))) + + # An explicit `vertex.weights` overrides the attribute, + # flipping the selection to the triangle. + res <- weighted_cliques( + g, + vertex.weights = w, + min.weight = 6, + max.weight = 9, + maximal = TRUE + ) + expect_identical(lapply(res, as.numeric), list(c(1, 2, 3))) + + # Legacy positional arguments are recovered with a deprecation warning. + lifecycle::expect_deprecated( + res <- weighted_cliques(g, w, 6) + ) + expect_identical(res, weighted_cliques(g, vertex.weights = w, min.weight = 6)) +}) + +test_that("largest_weighted_cliques() covers vertex.weights", { + g <- make_weighted_clique_graph() + w <- c(3, 3, 3, 1, 1) + + # Attribute weights favor the edge, + # the explicit `vertex.weights` vector overrides them. + expect_identical( + lapply(largest_weighted_cliques(g), as.numeric), + list(c(4, 5)) + ) + expect_identical( + lapply(largest_weighted_cliques(g, vertex.weights = w), as.numeric), + list(c(1, 2, 3)) + ) + + # Legacy positional `vertex.weights` is recovered with a deprecation warning. + lifecycle::expect_deprecated( + res <- largest_weighted_cliques(g, w) + ) + expect_identical(res, largest_weighted_cliques(g, vertex.weights = w)) +}) + +test_that("weighted_clique_num() covers vertex.weights", { + g <- make_weighted_clique_graph() + w <- c(3, 3, 3, 1, 1) + + # Attribute weights favor the edge, + # the explicit `vertex.weights` vector overrides them. + expect_equal(weighted_clique_num(g), 8) + expect_equal(weighted_clique_num(g, vertex.weights = w), 9) + + # Legacy positional `vertex.weights` is recovered with a deprecation warning. + lifecycle::expect_deprecated( + res <- weighted_clique_num(g, w) + ) + expect_identical(res, weighted_clique_num(g, vertex.weights = w)) +}) diff --git a/tools/migrations/cliques.R b/tools/migrations/cliques.R new file mode 100644 index 00000000000..9a63c393a80 --- /dev/null +++ b/tools/migrations/cliques.R @@ -0,0 +1,73 @@ +# Argument-signature migrations: cliques +# Schema: see tools/migrations/README.md. Regenerate with: +# Rscript tools/generate-migrations.R + +migrations <- list( + clique_size_counts = list( + old = function(graph, min, max, maximal) {}, + new = function( + graph, + ..., + min = 0, + max = 0, + maximal = FALSE + ) {}, + when = "3.0.0" + ), + + count_max_cliques = list( + old = function(graph, min, max, subset) {}, + new = function( + graph, + min = NULL, + max = NULL, + ..., + subset = NULL + ) {}, + when = "3.0.0" + ), + + is_clique = list( + old = function(graph, candidate, directed) {}, + new = function( + graph, + candidate, + ..., + directed = FALSE + ) {}, + when = "3.0.0" + ), + + largest_weighted_cliques = list( + old = function(graph, vertex.weights) {}, + new = function( + graph, + ..., + vertex.weights = NULL + ) {}, + when = "3.0.0" + ), + + weighted_clique_num = list( + old = function(graph, vertex.weights) {}, + new = function( + graph, + ..., + vertex.weights = NULL + ) {}, + when = "3.0.0" + ), + + weighted_cliques = list( + old = function(graph, vertex.weights, min.weight, max.weight, maximal) {}, + new = function( + graph, + ..., + vertex.weights = NULL, + min.weight = 0, + max.weight = 0, + maximal = FALSE + ) {}, + when = "3.0.0" + ) +)