Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ export(is_max_matching)
export(is_min_separator)
export(is_named)
export(is_printer_callback)
export(is_same_graph)
export(is_separator)
export(is_simple)
export(is_subgraph_isomorphic_to)
Expand Down
147 changes: 147 additions & 0 deletions R/iterators.R
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ get_es_graph_id <- get_vs_graph_id <- function(seq) {
#' @inheritParams rlang::args_dots_empty
#' @param attrs Whether to compare the attributes of the graphs
#' @return Logical scalar
#' @seealso [is_same_graph()] to ignore the order of vertices and edges,
#' [isomorphic()] to ignore vertex labels altogether.
#' @export
identical_graphs <- function(
g1,
Expand Down Expand Up @@ -124,6 +126,151 @@ identical_graphs <- function(
.Call(Rx_igraph_identical_graphs, g1, g2, as.logical(attrs))
}

#' Decide if two graphs are the same as labelled graphs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the title is unclear to me

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminology is standard in math and network science. I think this is a good title.

#'
#' @description
#' Two graphs are the same as labelled graphs if they have the same
#' directedness, the same vertices and the same edges.
#' Unlike [identical_graphs()], the order in which vertices and edges are
#' stored does not matter.
#' Unlike [isomorphic()], vertices are not relabelled.
#'
#' @details
#' Vertices can be identified by their index or by their name,
#' and `use_names` controls which one is used.
#' If `use_names` is `TRUE` and both graphs have a `name` vertex attribute,
#' a vertex of `g1` corresponds to the vertex with the same name in `g2`,
#' wherever it is stored.
#' Vertex names must be unique in both graphs in this case.
#' Otherwise, the \eqn{i}-th vertex of `g1` corresponds to the
#' \eqn{i}-th vertex of `g2`.
#'
#' Two graphs have the same edges if every edge occurs equally often in both.
#' The order of the edges does not matter,
#' and neither does the order of the two endpoints of an undirected edge.
#'
#' Vertex attributes listed in `vertex_attrs` must also be identical,
#' after matching up the vertices.
#' They are compared exactly, with [identical()],
#' so for example the integer `1L` and the double `1` are different values.
#' Graph and edge attributes are never compared,
#' and neither are vertex attributes other than those in `vertex_attrs`.
#'
#' The three ways of comparing graphs answer different questions:
#' - [identical_graphs()]: do the two objects store exactly the same data,
#' in the same order?
#' This is mostly useful in tests,
#' or before transferring edge attributes from one graph to the other by
#' position.
#' - `is_same_graph()`: do the same vertices have the same connections?
#' - [isomorphic()]: is there any way to relabel the vertices of one graph
#' so that it becomes the same as the other?
#'
#' Each check is stricter than the next one:
#' identical graphs are always the same,
#' and graphs that are the same are always isomorphic,
#' but not the other way round.
#'
#' @param g1,g2 The two graphs.
#' @inheritParams rlang::args_dots_empty
#' @param use_names Logical scalar, whether to identify vertices by their
#' names if both graphs have a `name` vertex attribute.
#' @param vertex_attrs Character vector of vertex attribute names to compare,
#' or `NULL` to compare none.
#' Every attribute must exist in both graphs.
#' Including `"name"` is only meaningful with `use_names = FALSE`,
#' otherwise names match by construction.
#' @return A logical scalar, `TRUE` if the two graphs are the same.
#' @seealso [identical_graphs()] for comparing the internal representation,
#' [isomorphic()] for comparing graphs up to relabelling of the vertices.
#' @export
#' @examples
#' # 1-2, 3-2 and 2-3, 1-2 only differ in the order of the edges and of
#' # their endpoints, so they are the same graph.
#' g1 <- make_graph(c(1, 2, 3, 2), directed = FALSE)
#' g2 <- make_graph(c(2, 3, 1, 2), directed = FALSE)
#' identical_graphs(g1, g2)
#' is_same_graph(g1, g2)
#'
#' # 1-3, 2-3 is isomorphic to them, but not the same:
#' # it contains the edge 1-3, which g1 does not.
#' g3 <- make_graph(c(1, 3, 2, 3), directed = FALSE)
#' is_same_graph(g1, g3)
#' isomorphic(g1, g3)
#'
#' # After bringing both graphs into canonical form,
#' # is_same_graph() tests for isomorphism.
#' # For named graphs, use `use_names = FALSE`:
#' # matching vertices by name would undo the reordering.
#' canon <- function(g) permute(g, canonical_permutation(g)$labeling)
#' is_same_graph(canon(g1), canon(g3))
#'
#' # Named vertices stored in a different order
#' edges <- data.frame(from = c("a", "b"), to = c("b", "c"))
#' g4 <- graph_from_data_frame(edges, vertices = c("a", "b", "c"))
#' g5 <- graph_from_data_frame(edges, vertices = c("c", "b", "a"))
#' identical_graphs(g4, g5)
#' is_same_graph(g4, g5)
#' is_same_graph(g4, g5, use_names = FALSE)
#'
#' # Vertex-coloured graphs: same edges, different colours
#' star <- make_star(4, mode = "undirected")
#' c1 <- set_vertex_attr(star, "color", value = c(1, 2, 2, 2))
#' c2 <- set_vertex_attr(star, "color", value = c(1, 3, 3, 3))
#' is_same_graph(c1, c2)
#' is_same_graph(c1, c2, vertex_attrs = "color")
is_same_graph <- function(g1, g2, ..., use_names = TRUE, vertex_attrs = NULL) {
check_dots_empty()
ensure_igraph(g1)
ensure_igraph(g2)
check_bool(use_names)
check_character(vertex_attrs, allow_na = FALSE, allow_null = TRUE)

missing1 <- setdiff(vertex_attrs, vertex_attr_names(g1))
missing2 <- setdiff(vertex_attrs, vertex_attr_names(g2))
if (length(missing1) > 0 || length(missing2) > 0) {
cli::cli_abort(c(
"All {.arg vertex_attrs} must exist in both graphs.",
x = if (length(missing1) > 0) "Missing in {.arg g1}: {.val {missing1}}.",
x = if (length(missing2) > 0) "Missing in {.arg g2}: {.val {missing2}}."
))
}

if (use_names && xor(is_named(g1), is_named(g2))) {
cli::cli_warn(c(
"Only one of the graphs has vertex names, matching vertices by their IDs.",
i = "Use {.code use_names = FALSE} to silence this warning."
))
}

if (use_names && is_named(g1) && is_named(g2)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should there be a warning when use_names but they are not both named?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No yes or no from me, just mentioning that in some circumstances vertex indices are auto-converted to string vertex names. So some people may expect to be able to treat the vertex indices of unnamed graphs as vertex "names". Doing such things doesn't make me very comfortable though, so again no "yes" or "no" from me.

names1 <- V(g1)$name
names2 <- V(g2)$name
if (anyDuplicated(names1) || anyDuplicated(names2)) {
cli::cli_abort(c(
"Vertex names must be unique to match vertices by name.",
i = "Use {.code use_names = FALSE} to match vertices by their IDs."
))
}
if (length(names1) != length(names2) || !setequal(names1, names2)) {
return(FALSE)
}
g2 <- permute(g2, match(names2, names1))
}

if (!is_same_graph_impl(graph1 = g1, graph2 = g2)) {
return(FALSE)
}

for (attr in vertex_attrs) {
if (!identical(vertex_attr(g1, attr), vertex_attr(g2, attr))) {
return(FALSE)
}
}

TRUE
}

add_vses_graph_ref <- function(vses, graph) {
ref <- get_vs_ref(graph)
if (!is.null(ref)) {
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ reference:
- contents:
- graph_id
- identical_graphs
- is_same_graph

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, should we document the three functions (identical, same, isomorphic) on the same page?

- is_igraph
- is_named
- is_weighted
Expand Down
4 changes: 4 additions & 0 deletions man/identical_graphs.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions man/is_same_graph.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions tests/testthat/_snaps/aaa-auto.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@
Error in `ensure_igraph()`:
! Must provide a graph object (provided `NULL`).

# is_same_graph_impl basic

Code
is_same_graph_impl(graph1 = g1, graph2 = g2)
Output
[1] TRUE

# is_same_graph_impl errors

Code
is_same_graph_impl(graph1 = NULL, graph2 = make_ring(3))
Condition
Error in `ensure_igraph()`:
! Must provide a graph object (provided `NULL`).

# delete_vertices_idx_impl basic

Code
Expand Down
59 changes: 59 additions & 0 deletions tests/testthat/_snaps/iterators.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,62 @@
# is_same_graph() uses IDs unless both graphs are named

Code
res <- is_same_graph(g1, g2)
Condition
Warning:
Only one of the graphs has vertex names, matching vertices by their IDs.
i Use `use_names = FALSE` to silence this warning.

# is_same_graph() errors

Code
is_same_graph(dup, dup)
Condition
Error in `is_same_graph()`:
! Vertex names must be unique to match vertices by name.
i Use `use_names = FALSE` to match vertices by their IDs.
Code
is_same_graph(g, g, TRUE)
Condition
Error in `is_same_graph()`:
! `...` must be empty.
x Problematic argument:
* ..1 = TRUE
i Did you forget to name an argument?
Code
is_same_graph(g, g, use_names = NA)
Condition
Error in `is_same_graph()`:
! `use_names` must be `TRUE` or `FALSE`, not `NA`.
Code
is_same_graph(g, NULL)
Condition
Error in `ensure_igraph()`:
! Must provide a graph object (provided `NULL`).
Code
is_same_graph(g, g, vertex_attrs = 1)
Condition
Error in `is_same_graph()`:
! `vertex_attrs` must be a character vector or `NULL`, not the number 1.
Code
is_same_graph(g, g, vertex_attrs = NA_character_)
Condition
Error in `is_same_graph()`:
! `vertex_attrs` can't contain NA values.
Code
is_same_graph(g, set_vertex_attr(g, "color", value = 1:3), vertex_attrs = "color")
Condition
Error in `is_same_graph()`:
! All `vertex_attrs` must exist in both graphs.
x Missing in `g1`: "color".
Code
is_same_graph(g, g, vertex_attrs = c("color", "size"))
Condition
Error in `is_same_graph()`:
! All `vertex_attrs` must exist in both graphs.
x Missing in `g1`: "color" and "size".
x Missing in `g2`: "color" and "size".

# printing connected vs/es works

Code
Expand Down
Loading
Loading