Skip to content

Commit cf26ffa

Browse files
committed
feat: Add cycles-related functions
1 parent bd321a1 commit cf26ffa

10 files changed

Lines changed: 206 additions & 0 deletions

File tree

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,12 @@ export(igraph_version)
8787
export(incident)
8888
export(induced_subgraph)
8989
export(induced_subgraph_edges)
90+
export(is_acyclic)
9091
export(is_biconnected)
9192
export(is_connected)
93+
export(is_dag)
9294
export(is_directed)
95+
export(is_eulerian)
9396
export(is_igraph)
9497
export(is_simple)
9598
export(is_sink)

R/cpp11.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ is_biconnected_ <- function(graph) {
168168
.Call(`_igraphlite_is_biconnected_`, graph)
169169
}
170170

171+
is_dag_ <- function(graph) {
172+
.Call(`_igraphlite_is_dag_`, graph)
173+
}
174+
175+
is_eulerian_ <- function(graph) {
176+
.Call(`_igraphlite_is_eulerian_`, graph)
177+
}
178+
171179
as_adjlist_ <- function(graph, mode, loops, multiple) {
172180
.Call(`_igraphlite_as_adjlist_`, graph, mode, loops, multiple)
173181
}
@@ -440,6 +448,10 @@ is_simple_ <- function(graph, directed) {
440448
.Call(`_igraphlite_is_simple_`, graph, directed)
441449
}
442450

451+
is_acyclic_ <- function(graph) {
452+
.Call(`_igraphlite_is_acyclic_`, graph)
453+
}
454+
443455
has_loop_ <- function(graph) {
444456
.Call(`_igraphlite_has_loop_`, graph)
445457
}

R/cycles.R

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#' Acyclic graphs and feedback sets
2+
#'
3+
#' A directed acyclic graph (DAG) is a directed graph with no directed cycles.
4+
#' For directed graphs, only directed cycles are considered.
5+
#' @inheritParams common_params
6+
#' @returns A logical value.
7+
#' @source <https://igraph.org/c/doc/igraph-Cycles.html#acyclic-graphs-feedback-sets>
8+
#' @rdname cycles-acyclic
9+
#' @export
10+
#' @examples
11+
#' dag = graph_tree(5L)
12+
#' tree = graph_tree(5L, mode = 2L)
13+
#' ring = graph_ring(5L)
14+
#'
15+
#' is_dag(dag)
16+
#' is_dag(tree)
17+
#' is_dag(ring)
18+
#'
19+
#' is_acyclic(dag)
20+
#' is_acyclic(tree)
21+
#' is_acyclic(ring)
22+
is_dag = function(graph) {
23+
.Call(`_igraphlite_is_dag_`, graph)
24+
}
25+
26+
#' @rdname cycles-acyclic
27+
#' @export
28+
is_acyclic = function(graph) {
29+
.Call(`_igraphlite_is_acyclic_`, graph)
30+
}
31+
32+
#' Eulerian cycles and paths
33+
#'
34+
#' An Eulerian path traverses each edge of the graph precisely once.
35+
#' A closed Eulerian path is referred to as an Eulerian cycle.
36+
#' @inheritParams common_params
37+
#' @returns A logical value with two elements: "has a path" and "has a cycle".
38+
#' @source <https://igraph.org/c/doc/igraph-Cycles.html#eulerian-cycles>
39+
#' @rdname cycles-eulerian
40+
#' @export
41+
#' @examples
42+
#' is_eulerian(graph_tree(5L))
43+
#'
44+
#' is_eulerian(graph_square_lattice(5L))
45+
#'
46+
#' is_eulerian(graph_ring(5L))
47+
is_eulerian = function(graph) {
48+
.Call(`_igraphlite_is_eulerian_`, graph)
49+
}

_pkgdown.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ reference:
5656
- components
5757
- centrality
5858
- is_simple
59+
- title: Graph cycles
60+
desc: https://igraph.org/c/doc/igraph-Cycles.html
61+
- contents:
62+
- is_acyclic
63+
- is_eulerian
5964
- title: Generating Layouts for Graph Drawing
6065
desc: https://igraph.org/c/doc/igraph-Layout.html
6166
- contents:

man/cycles-acyclic.Rd

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/cycles-eulerian.Rd

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cpp11.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,20 @@ extern "C" SEXP _igraphlite_is_biconnected_(SEXP graph) {
307307
return cpp11::as_sexp(is_biconnected_(cpp11::as_cpp<cpp11::decay_t<const cpp11::external_pointer<IGraph>>>(graph)));
308308
END_CPP11
309309
}
310+
// cycles.cpp
311+
bool is_dag_(const cpp11::external_pointer<IGraph> graph);
312+
extern "C" SEXP _igraphlite_is_dag_(SEXP graph) {
313+
BEGIN_CPP11
314+
return cpp11::as_sexp(is_dag_(cpp11::as_cpp<cpp11::decay_t<const cpp11::external_pointer<IGraph>>>(graph)));
315+
END_CPP11
316+
}
317+
// cycles.cpp
318+
SEXP is_eulerian_(const cpp11::external_pointer<IGraph> graph);
319+
extern "C" SEXP _igraphlite_is_eulerian_(SEXP graph) {
320+
BEGIN_CPP11
321+
return cpp11::as_sexp(is_eulerian_(cpp11::as_cpp<cpp11::decay_t<const cpp11::external_pointer<IGraph>>>(graph)));
322+
END_CPP11
323+
}
310324
// data.cpp
311325
SEXP as_adjlist_(const cpp11::external_pointer<IGraph> graph, const int mode, const int loops, const bool multiple);
312326
extern "C" SEXP _igraphlite_as_adjlist_(SEXP graph, SEXP mode, SEXP loops, SEXP multiple) {
@@ -796,6 +810,13 @@ extern "C" SEXP _igraphlite_is_simple_(SEXP graph, SEXP directed) {
796810
END_CPP11
797811
}
798812
// structural.cpp
813+
bool is_acyclic_(const cpp11::external_pointer<IGraph> graph);
814+
extern "C" SEXP _igraphlite_is_acyclic_(SEXP graph) {
815+
BEGIN_CPP11
816+
return cpp11::as_sexp(is_acyclic_(cpp11::as_cpp<cpp11::decay_t<const cpp11::external_pointer<IGraph>>>(graph)));
817+
END_CPP11
818+
}
819+
// structural.cpp
799820
bool has_loop_(const cpp11::external_pointer<IGraph> graph);
800821
extern "C" SEXP _igraphlite_has_loop_(SEXP graph) {
801822
BEGIN_CPP11
@@ -941,9 +962,12 @@ static const R_CallMethodDef CallEntries[] = {
941962
{"_igraphlite_incident_", (DL_FUNC) &_igraphlite_incident_, 4},
942963
{"_igraphlite_induced_subgraph_", (DL_FUNC) &_igraphlite_induced_subgraph_, 3},
943964
{"_igraphlite_induced_subgraph_edges_", (DL_FUNC) &_igraphlite_induced_subgraph_edges_, 2},
965+
{"_igraphlite_is_acyclic_", (DL_FUNC) &_igraphlite_is_acyclic_, 1},
944966
{"_igraphlite_is_biconnected_", (DL_FUNC) &_igraphlite_is_biconnected_, 1},
945967
{"_igraphlite_is_connected_", (DL_FUNC) &_igraphlite_is_connected_, 2},
968+
{"_igraphlite_is_dag_", (DL_FUNC) &_igraphlite_is_dag_, 1},
946969
{"_igraphlite_is_directed_", (DL_FUNC) &_igraphlite_is_directed_, 1},
970+
{"_igraphlite_is_eulerian_", (DL_FUNC) &_igraphlite_is_eulerian_, 1},
947971
{"_igraphlite_is_simple_", (DL_FUNC) &_igraphlite_is_simple_, 2},
948972
{"_igraphlite_k_regular_game_", (DL_FUNC) &_igraphlite_k_regular_game_, 4},
949973
{"_igraphlite_layout_circle_", (DL_FUNC) &_igraphlite_layout_circle_, 2},

src/cycles.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// <https://igraph.org/c/doc/igraph-Cycles.html>
2+
#include "igraph.hpp"
3+
4+
#include <igraph/igraph_cycles.h>
5+
#include <igraph/igraph_eulerian.h>
6+
7+
#include <cpp11/logicals.hpp>
8+
9+
[[cpp11::register]] bool
10+
is_dag_(const cpp11::external_pointer<IGraph> graph) {
11+
igraph_bool_t res;
12+
igraph_is_dag(graph->data(), &res);
13+
return res;
14+
}
15+
16+
[[cpp11::register]] SEXP
17+
is_eulerian_(const cpp11::external_pointer<IGraph> graph) {
18+
igraph_bool_t has_path;
19+
igraph_bool_t has_cycle;
20+
igraph_is_eulerian(graph->data(), &has_path, &has_cycle);
21+
return cpp11::writable::logicals{has_path, has_cycle};
22+
}

src/structural.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ is_simple_(const cpp11::external_pointer<IGraph> graph, const bool directed) {
2020
return res;
2121
}
2222

23+
[[cpp11::register]] bool
24+
is_acyclic_(const cpp11::external_pointer<IGraph> graph) {
25+
igraph_bool_t res;
26+
igraph_is_acyclic(graph->data(), &res);
27+
return res;
28+
}
29+
2330
[[cpp11::register]] bool
2431
has_loop_(const cpp11::external_pointer<IGraph> graph) {
2532
igraph_bool_t res;

tests/testthat/test-cycles.R

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
test_that("acyclic tests work", {
2+
dag = graph_tree(5L)
3+
tree = graph_tree(5L, mode = 2L)
4+
ring = graph_ring(5L)
5+
6+
expect_true(is_dag(dag))
7+
expect_false(is_dag(tree))
8+
expect_false(is_dag(ring))
9+
10+
expect_true(is_acyclic(dag))
11+
expect_true(is_acyclic(tree))
12+
expect_false(is_acyclic(ring))
13+
})
14+
15+
test_that("eulerian test works", {
16+
expect_identical(is_eulerian(graph_tree(5L)), c(FALSE, FALSE))
17+
expect_identical(is_eulerian(graph_square_lattice(5L)), c(TRUE, FALSE))
18+
expect_identical(is_eulerian(graph_ring(5L)), c(TRUE, TRUE))
19+
})

0 commit comments

Comments
 (0)