-
-
Notifications
You must be signed in to change notification settings - Fork 207
feat: Add is_same_graph() to compare graphs regardless of vertex and edge order
#2922
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
381152b
a228258
57d404a
5b700d5
6d35327
4f39645
843c3c4
df44d42
98c0d34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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 | ||
| #' | ||
| #' @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)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should there be a warning when
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,7 @@ reference: | |
| - contents: | ||
| - graph_id | ||
| - identical_graphs | ||
| - is_same_graph | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.