use super::canonicalize;
use crate::algorithms::isomorphism::vf2::Vf2Isomorphism;
use crate::core::{Graph, IgraphError, IgraphResult};
pub fn isomorphic_bliss(
graph1: &Graph,
graph2: &Graph,
vertex_color1: Option<&[u32]>,
vertex_color2: Option<&[u32]>,
) -> IgraphResult<Vf2Isomorphism> {
if graph1.is_directed() != graph2.is_directed() {
return Err(IgraphError::InvalidArgument(
"cannot compare directed and undirected graphs".to_owned(),
));
}
let (color1, color2) = if vertex_color1.is_some() == vertex_color2.is_some() {
(vertex_color1, vertex_color2)
} else {
(None, None)
};
let n1 = graph1.vcount() as usize;
let n2 = graph2.vcount() as usize;
let canon1 = canonicalize(graph1, color1)?;
let canon2 = canonicalize(graph2, color2)?;
let not_iso = || Vf2Isomorphism {
iso: false,
map12: Vec::new(),
map21: Vec::new(),
};
if n1 != n2 || canon1.certificate != canon2.certificate {
return Ok(not_iso());
}
let mut order1 = vec![0u32; n1];
for (v, &pos) in canon1.labeling.iter().enumerate() {
order1[pos as usize] = v as u32;
}
let mut order2 = vec![0u32; n2];
for (v, &pos) in canon2.labeling.iter().enumerate() {
order2[pos as usize] = v as u32;
}
if let (Some(c1), Some(c2)) = (color1, color2) {
for pos in 0..n1 {
if c1[order1[pos] as usize] != c2[order2[pos] as usize] {
return Ok(not_iso());
}
}
}
let mut map12 = vec![0u32; n1];
let mut map21 = vec![0u32; n2];
for (v1, &pos) in canon1.labeling.iter().enumerate() {
let v2 = order2[pos as usize];
map12[v1] = v2;
map21[v2 as usize] = v1 as u32;
}
Ok(Vf2Isomorphism {
iso: true,
map12,
map21,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn triangle(perm: [u32; 3]) -> Graph {
let mut g = Graph::new(3, false).expect("graph");
g.add_edge(perm[0], perm[1]).expect("edge");
g.add_edge(perm[1], perm[2]).expect("edge");
g.add_edge(perm[2], perm[0]).expect("edge");
g
}
fn path(n: u32) -> Graph {
let mut g = Graph::new(n, false).expect("graph");
for i in 0..n.saturating_sub(1) {
g.add_edge(i, i + 1).expect("edge");
}
g
}
fn complete(n: u32) -> Graph {
let mut g = Graph::new(n, false).expect("graph");
for a in 0..n {
for b in (a + 1)..n {
g.add_edge(a, b).expect("edge");
}
}
g
}
fn directed_cycle(n: u32) -> Graph {
let mut g = Graph::new(n, true).expect("graph");
for i in 0..n {
g.add_edge(i, (i + 1) % n).expect("edge");
}
g
}
#[allow(clippy::many_single_char_names)]
fn assert_valid_map(a: &Graph, b: &Graph, r: &Vf2Isomorphism) {
let n = a.vcount() as usize;
assert_eq!(r.map12.len(), n);
assert_eq!(r.map21.len(), b.vcount() as usize);
let mut seen = vec![false; n];
for (v, &iv) in r.map12.iter().enumerate() {
assert!(!seen[iv as usize], "map12 not injective");
seen[iv as usize] = true;
assert_eq!(r.map21[iv as usize], v as u32, "map21 not inverse of map12");
}
let b_edges: std::collections::HashSet<(u32, u32)> = (0..b.ecount())
.map(|e| b.edge(e as u32).expect("edge"))
.map(|(u, v)| {
if b.is_directed() || u <= v {
(u, v)
} else {
(v, u)
}
})
.collect();
for e in 0..a.ecount() {
let (u, v) = a.edge(e as u32).expect("edge");
let (iu, iv) = (r.map12[u as usize], r.map12[v as usize]);
let key = if a.is_directed() || iu <= iv {
(iu, iv)
} else {
(iv, iu)
};
assert!(b_edges.contains(&key), "edge not preserved by map");
}
}
#[test]
fn relabelled_triangles_are_isomorphic() {
let a = triangle([0, 1, 2]);
let b = triangle([2, 0, 1]);
let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
assert!(r.iso);
assert_valid_map(&a, &b, &r);
}
#[test]
fn triangle_is_not_a_path() {
let r = isomorphic_bliss(&triangle([0, 1, 2]), &path(3), None, None).expect("bliss");
assert!(!r.iso);
assert!(r.map12.is_empty());
assert!(r.map21.is_empty());
}
#[test]
fn different_vertex_counts_are_not_isomorphic() {
let r = isomorphic_bliss(&complete(4), &complete(5), None, None).expect("bliss");
assert!(!r.iso);
}
#[test]
fn complete_graphs_match_under_any_relabelling() {
let a = complete(5);
let b = complete(5);
let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
assert!(r.iso);
assert_valid_map(&a, &b, &r);
}
#[test]
fn directed_cycles_respect_orientation() {
let a = directed_cycle(4);
let b = directed_cycle(4);
let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
assert!(r.iso);
assert_valid_map(&a, &b, &r);
}
#[test]
fn directedness_mismatch_is_an_error() {
let und = path(3);
let dir = directed_cycle(3);
assert!(isomorphic_bliss(&und, &dir, None, None).is_err());
}
#[test]
fn colours_must_match_for_isomorphism() {
let a = path(3);
let b = path(3);
let ca = [0u32, 1, 0];
let cb = [1u32, 0, 1];
let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
assert!(!r.iso);
}
#[test]
fn colour_matching_uses_raw_values_not_class_structure() {
let a = path(3);
let b = path(3);
let ca = [5u32, 6, 5];
let cb = [7u32, 8, 7];
let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
assert!(!r.iso);
}
#[test]
fn matching_colours_yield_isomorphism() {
let a = path(3);
let b = path(3);
let ca = [0u32, 1, 0];
let cb = [0u32, 1, 0];
let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
assert!(r.iso);
assert_valid_map(&a, &b, &r);
}
#[test]
fn one_sided_colour_is_ignored() {
let a = triangle([0, 1, 2]);
let b = triangle([1, 2, 0]);
let ca = [0u32, 1, 2];
let r = isomorphic_bliss(&a, &b, Some(&ca), None).expect("bliss");
assert!(r.iso);
}
#[test]
fn wrong_colour_length_is_an_error() {
let a = triangle([0, 1, 2]);
let b = triangle([0, 1, 2]);
let bad = [0u32, 0];
assert!(isomorphic_bliss(&a, &b, Some(&bad), Some(&[0, 0, 0])).is_err());
}
#[test]
fn rejects_multigraph() {
let mut a = Graph::new(2, false).expect("graph");
a.add_edge(0, 1).expect("edge");
a.add_edge(0, 1).expect("edge");
let b = path(2);
assert!(isomorphic_bliss(&a, &b, None, None).is_err());
}
}