From e63943be4c7b908283bfa058b6db31dc3c190931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20Horva=CC=81t?= Date: Mon, 11 Nov 2024 11:31:40 +0000 Subject: [PATCH] feat: `simple_cycles()` --- NAMESPACE | 1 + R/aaa-auto.R | 19 ----------- R/cycles.R | 56 ++++++++++++++++++++++++++++++ man/feedback_arc_set.Rd | 3 +- man/find_cycle.Rd | 3 +- man/girth.Rd | 3 +- man/has_eulerian_path.Rd | 3 +- man/is_acyclic.Rd | 3 +- man/is_dag.Rd | 3 +- man/simple_cycles.Rd | 60 +++++++++++++++++++++++++++++++++ tools/stimulus/functions-R.yaml | 1 + 11 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 man/simple_cycles.Rd diff --git a/NAMESPACE b/NAMESPACE index fc9fd04af0..d8b1def76a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -801,6 +801,7 @@ export(similarity) export(similarity.dice) export(similarity.invlogweighted) export(similarity.jaccard) +export(simple_cycles) export(simplified) export(simplify) export(simplify_and_colorize) diff --git a/R/aaa-auto.R b/R/aaa-auto.R index a0a4c5c571..6cc18e9906 100644 --- a/R/aaa-auto.R +++ b/R/aaa-auto.R @@ -3580,25 +3580,6 @@ find_cycle_impl <- function(graph, mode=c("out", "in", "all", "total")) { res } -simple_cycles_impl <- function(graph, mode=c("out", "in", "all", "total"), min.cycle.length=-1, max.cycle.length=-1) { - # Argument checks - ensure_igraph(graph) - mode <- switch(igraph.match.arg(mode), "out"=1L, "in"=2L, "all"=3L, "total"=3L) - min.cycle.length <- as.numeric(min.cycle.length) - max.cycle.length <- as.numeric(max.cycle.length) - - on.exit( .Call(R_igraph_finalizer) ) - # Function call - res <- .Call(R_igraph_simple_cycles, graph, mode, min.cycle.length, max.cycle.length) - if (igraph_opt("return.vs.es")) { - res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) - } - if (igraph_opt("return.vs.es")) { - res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) - } - res -} - is_eulerian_impl <- function(graph) { # Argument checks ensure_igraph(graph) diff --git a/R/cycles.R b/R/cycles.R index 9367778d92..fc6ae629c6 100644 --- a/R/cycles.R +++ b/R/cycles.R @@ -50,3 +50,59 @@ #' @export find_cycle <- find_cycle_impl + + +#' Finds all simple cycles in a graph. +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' This function lists all simple cycles in a graph within a range of cycle +#' lengths. A cycle is called simple if it has no repeated vertices. +#' +#' Multi-edges and self-loops are taken into account. Note that graph can have +#' exponentially many cycles and the presence of multi-edges exacerbates this +#' combinatorial explosion. +#' +#' @inheritParams find_cycle +#' @param min Lower limit on cycle lengths to consider. `NULL` means no limit. +#' @param max Upper limit on cycle lengths to consider. `NULL` means no limit. +#' @return A named list, with two entries: +#' \item{vertices}{The list of cycles in terms of their vertices.} +#' \item{edges}{The list of cycles in terms of their edges.} +#' @keywords graphs +#' @examples +#' +#' g <- graph_from_literal(A-+B-+C-+A-+D-+E+-F-+A, E-+E, A-+F, simplify=F) +#' simple_cycles(g) +#' simple_cycles(g, mode = "all") # ignore edge directions +#' simple_cycles(g, mode = "all", min = 2, max = 3) # limit cycle lengths +#' +#' @family cycles +#' @cdocs igraph_simple_cycles +#' @export + +simple_cycles <- function(graph, mode=c("out", "in", "all", "total"), min=NULL, max=NULL) { + # Argument checks + ensure_igraph(graph) + mode <- switch(igraph.match.arg(mode), "out"=1L, "in"=2L, "all"=3L, "total"=3L) + + if (is.null(min)) { + min <- -1 + } + + if (is.null(max)) { + max <- -1 + } + + on.exit( .Call(R_igraph_finalizer) ) + # Function call + res <- .Call(R_igraph_simple_cycles, graph, mode, as.numeric(min), as.numeric(max)) + if (igraph_opt("return.vs.es")) { + res$vertices <- lapply(res$vertices, unsafe_create_vs, graph = graph, verts = V(graph)) + } + if (igraph_opt("return.vs.es")) { + res$edges <- lapply(res$edges, unsafe_create_es, graph = graph, es = E(graph)) + } + res +} diff --git a/man/feedback_arc_set.Rd b/man/feedback_arc_set.Rd index c74c318ab5..bb857ddd80 100644 --- a/man/feedback_arc_set.Rd +++ b/man/feedback_arc_set.Rd @@ -80,7 +80,8 @@ Graph cycles \code{\link{girth}()}, \code{\link{has_eulerian_path}()}, \code{\link{is_acyclic}()}, -\code{\link{is_dag}()} +\code{\link{is_dag}()}, +\code{\link{simple_cycles}()} } \concept{cycles} \concept{structural.properties} diff --git a/man/find_cycle.Rd b/man/find_cycle.Rd index 249e4a5dd7..00eb3cd4c4 100644 --- a/man/find_cycle.Rd +++ b/man/find_cycle.Rd @@ -43,7 +43,8 @@ Graph cycles \code{\link{girth}()}, \code{\link{has_eulerian_path}()}, \code{\link{is_acyclic}()}, -\code{\link{is_dag}()} +\code{\link{is_dag}()}, +\code{\link{simple_cycles}()} } \concept{cycles} \keyword{graphs} diff --git a/man/girth.Rd b/man/girth.Rd index d99ad8eca0..6ca8bc5c8b 100644 --- a/man/girth.Rd +++ b/man/girth.Rd @@ -81,7 +81,8 @@ Graph cycles \code{\link{find_cycle}()}, \code{\link{has_eulerian_path}()}, \code{\link{is_acyclic}()}, -\code{\link{is_dag}()} +\code{\link{is_dag}()}, +\code{\link{simple_cycles}()} } \author{ Gabor Csardi \email{csardi.gabor@gmail.com} diff --git a/man/has_eulerian_path.Rd b/man/has_eulerian_path.Rd index cee1bf5e2e..fbf8fa3de3 100644 --- a/man/has_eulerian_path.Rd +++ b/man/has_eulerian_path.Rd @@ -62,7 +62,8 @@ Graph cycles \code{\link{find_cycle}()}, \code{\link{girth}()}, \code{\link{is_acyclic}()}, -\code{\link{is_dag}()} +\code{\link{is_dag}()}, +\code{\link{simple_cycles}()} } \concept{cycles} \keyword{graphs} diff --git a/man/is_acyclic.Rd b/man/is_acyclic.Rd index 5c54dbee3a..721670678f 100644 --- a/man/is_acyclic.Rd +++ b/man/is_acyclic.Rd @@ -34,7 +34,8 @@ Graph cycles \code{\link{find_cycle}()}, \code{\link{girth}()}, \code{\link{has_eulerian_path}()}, -\code{\link{is_dag}()} +\code{\link{is_dag}()}, +\code{\link{simple_cycles}()} Other structural.properties: \code{\link{bfs}()}, diff --git a/man/is_dag.Rd b/man/is_dag.Rd index e3beca2eab..96a541b3d4 100644 --- a/man/is_dag.Rd +++ b/man/is_dag.Rd @@ -34,7 +34,8 @@ Graph cycles \code{\link{find_cycle}()}, \code{\link{girth}()}, \code{\link{has_eulerian_path}()}, -\code{\link{is_acyclic}()} +\code{\link{is_acyclic}()}, +\code{\link{simple_cycles}()} Other structural.properties: \code{\link{bfs}()}, diff --git a/man/simple_cycles.Rd b/man/simple_cycles.Rd new file mode 100644 index 0000000000..919e389cfb --- /dev/null +++ b/man/simple_cycles.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/cycles.R +\name{simple_cycles} +\alias{simple_cycles} +\title{Finds all simple cycles in a graph.} +\usage{ +simple_cycles( + graph, + mode = c("out", "in", "all", "total"), + min = NULL, + max = NULL +) +} +\arguments{ +\item{graph}{The input graph.} + +\item{mode}{Character constant specifying how to handle directed graphs. +\code{out} follows edge directions, \verb{in} follows edges in the reverse direction, +and \code{all} ignores edge directions. Ignored in undirected graphs.} + +\item{min}{Lower limit on cycle lengths to consider. \code{NULL} means no limit.} + +\item{max}{Upper limit on cycle lengths to consider. \code{NULL} means no limit.} +} +\value{ +A named list, with two entries: +\item{vertices}{The list of cycles in terms of their vertices.} +\item{edges}{The list of cycles in terms of their edges.} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +This function lists all simple cycles in a graph within a range of cycle +lengths. A cycle is called simple if it has no repeated vertices. + +Multi-edges and self-loops are taken into account. Note that graph can have +exponentially many cycles and the presence of multi-edges exacerbates this +combinatorial explosion. +} +\examples{ + +g <- graph_from_literal(A-+B-+C-+A-+D-+E+-F-+A, E-+E, A-+F, simplify=F) +simple_cycles(g) +simple_cycles(g, mode = "all") # ignore edge directions +simple_cycles(g, mode = "all", min = 2, max = 3) # limit cycle lengths + +} +\seealso{ +Graph cycles +\code{\link{feedback_arc_set}()}, +\code{\link{find_cycle}()}, +\code{\link{girth}()}, +\code{\link{has_eulerian_path}()}, +\code{\link{is_acyclic}()}, +\code{\link{is_dag}()} +} +\concept{cycles} +\keyword{graphs} +\section{Related documentation in the C library}{\href{https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_simple_cycles}{\code{igraph_simple_cycles()}}.} + diff --git a/tools/stimulus/functions-R.yaml b/tools/stimulus/functions-R.yaml index 6054072f11..33e1cae05b 100644 --- a/tools/stimulus/functions-R.yaml +++ b/tools/stimulus/functions-R.yaml @@ -1464,6 +1464,7 @@ igraph_solve_lsap: igraph_find_cycle: igraph_simple_cycles: + IGNORE: RR igraph_simple_cycles_callback: IGNORE: RR, RC