#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
use super::vf2::{Flow, adjacency, contains_sorted, in_degree, out_degree, perform_pre_checks};
use crate::core::{Graph, IgraphError, IgraphResult};
#[derive(Debug, Clone)]
pub struct Vf2Subisomorphism {
pub iso: bool,
pub map21: Vec<u32>,
pub map12: Vec<Option<u32>>,
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
fn subiso_engine(
graph1: &Graph,
graph2: &Graph,
mut vertex_color1: Option<&[u32]>,
mut vertex_color2: Option<&[u32]>,
mut edge_color1: Option<&[u32]>,
mut edge_color2: Option<&[u32]>,
mut isohandler: impl FnMut(&[i64], &[i64]) -> Flow,
) -> IgraphResult<()> {
perform_pre_checks(graph1, graph2)?;
let no_of_nodes1 = i64::from(graph1.vcount());
let no_of_nodes2 = i64::from(graph2.vcount());
let no_of_edges1 = graph1.ecount() as i64;
let no_of_edges2 = graph2.ecount() as i64;
if no_of_nodes1 < no_of_nodes2 || no_of_edges1 < no_of_edges2 {
return Ok(());
}
if vertex_color1.is_some() != vertex_color2.is_some() {
vertex_color1 = None;
vertex_color2 = None;
}
if edge_color1.is_some() != edge_color2.is_some() {
edge_color1 = None;
edge_color2 = None;
}
if let (Some(c1), Some(c2)) = (vertex_color1, vertex_color2) {
if c1.len() as i64 != no_of_nodes1 || c2.len() as i64 != no_of_nodes2 {
return Err(IgraphError::InvalidArgument(
"invalid vertex color vector length".into(),
));
}
}
if let (Some(c1), Some(c2)) = (edge_color1, edge_color2) {
if c1.len() as i64 != no_of_edges1 || c2.len() as i64 != no_of_edges2 {
return Err(IgraphError::InvalidArgument(
"invalid edge color vector length".into(),
));
}
}
let n1 = no_of_nodes1 as usize;
let n2 = no_of_nodes2 as usize;
let inneis_1 = adjacency(graph1, true)?;
let outneis_1 = adjacency(graph1, false)?;
let inneis_2 = adjacency(graph2, true)?;
let outneis_2 = adjacency(graph2, false)?;
let mut indeg1 = vec![0i64; n1];
let mut outdeg1 = vec![0i64; n1];
for v in 0..no_of_nodes1 {
let vu = v as usize;
indeg1[vu] = in_degree(graph1, v as u32)?;
outdeg1[vu] = out_degree(graph1, v as u32)?;
}
let mut indeg2 = vec![0i64; n2];
let mut outdeg2 = vec![0i64; n2];
for v in 0..no_of_nodes2 {
let vu = v as usize;
indeg2[vu] = in_degree(graph2, v as u32)?;
outdeg2[vu] = out_degree(graph2, v as u32)?;
}
let mut core_1 = vec![-1i64; n1];
let mut core_2 = vec![-1i64; n2];
let mut in_1 = vec![0i64; n1];
let mut in_2 = vec![0i64; n2];
let mut out_1 = vec![0i64; n1];
let mut out_2 = vec![0i64; n2];
let mut in_1_size = 0i64;
let mut in_2_size = 0i64;
let mut out_1_size = 0i64;
let mut out_2_size = 0i64;
let mut path: Vec<i64> = Vec::with_capacity(2 * n2);
let mut matched_nodes = 0i64;
let mut depth = 0i64;
let mut last1 = -1i64;
let mut last2 = -1i64;
while depth >= 0 {
let mut cand1 = -1i64;
let mut cand2 = -1i64;
if in_1_size < in_2_size || out_1_size < out_2_size {
} else if out_1_size > 0 && out_2_size > 0 {
if last2 >= 0 {
cand2 = last2;
} else {
let mut i = 0i64;
while cand2 < 0 && i < no_of_nodes2 {
if out_2[i as usize] > 0 && core_2[i as usize] < 0 {
cand2 = i;
}
i += 1;
}
}
let mut i = last1 + 1;
while cand1 < 0 && i < no_of_nodes1 {
if out_1[i as usize] > 0 && core_1[i as usize] < 0 {
cand1 = i;
}
i += 1;
}
} else if in_1_size > 0 && in_2_size > 0 {
if last2 >= 0 {
cand2 = last2;
} else {
let mut i = 0i64;
while cand2 < 0 && i < no_of_nodes2 {
if in_2[i as usize] > 0 && core_2[i as usize] < 0 {
cand2 = i;
}
i += 1;
}
}
let mut i = last1 + 1;
while cand1 < 0 && i < no_of_nodes1 {
if in_1[i as usize] > 0 && core_1[i as usize] < 0 {
cand1 = i;
}
i += 1;
}
} else {
if last2 >= 0 {
cand2 = last2;
} else {
let mut i = 0i64;
while cand2 < 0 && i < no_of_nodes2 {
if core_2[i as usize] < 0 {
cand2 = i;
}
i += 1;
}
}
let mut i = last1 + 1;
while cand1 < 0 && i < no_of_nodes1 {
if core_1[i as usize] < 0 {
cand1 = i;
}
i += 1;
}
}
if cand1 < 0 || cand2 < 0 {
if depth >= 1 {
last2 = path
.pop()
.ok_or(IgraphError::Internal("subiso: empty path"))?;
last1 = path
.pop()
.ok_or(IgraphError::Internal("subiso: empty path"))?;
let l1 = last1 as usize;
let l2 = last2 as usize;
matched_nodes -= 1;
core_1[l1] = -1;
core_2[l2] = -1;
if in_1[l1] != 0 {
in_1_size += 1;
}
if out_1[l1] != 0 {
out_1_size += 1;
}
if in_2[l2] != 0 {
in_2_size += 1;
}
if out_2[l2] != 0 {
out_2_size += 1;
}
for &node in &inneis_1[l1] {
if in_1[node as usize] == depth {
in_1[node as usize] = 0;
in_1_size -= 1;
}
}
for &node in &outneis_1[l1] {
if out_1[node as usize] == depth {
out_1[node as usize] = 0;
out_1_size -= 1;
}
}
for &node in &inneis_2[l2] {
if in_2[node as usize] == depth {
in_2[node as usize] = 0;
in_2_size -= 1;
}
}
for &node in &outneis_2[l2] {
if out_2[node as usize] == depth {
out_2[node as usize] = 0;
out_2_size -= 1;
}
}
}
depth -= 1;
} else {
let c1 = cand1 as usize;
let c2 = cand2 as usize;
let mut xin1 = 0i64;
let mut xin2 = 0i64;
let mut xout1 = 0i64;
let mut xout2 = 0i64;
let mut end = false;
if indeg1[c1] < indeg2[c2] || outdeg1[c1] < outdeg2[c2] {
end = true;
}
if let (Some(vc1), Some(vc2)) = (vertex_color1, vertex_color2) {
if vc1[c1] != vc2[c2] {
end = true;
}
}
for &node in &inneis_1[c1] {
if end {
break;
}
let nu = node as usize;
if core_1[nu] < 0 {
if in_1[nu] != 0 {
xin1 += 1;
}
if out_1[nu] != 0 {
xout1 += 1;
}
}
}
for &node in &outneis_1[c1] {
if end {
break;
}
let nu = node as usize;
if core_1[nu] < 0 {
if in_1[nu] != 0 {
xin1 += 1;
}
if out_1[nu] != 0 {
xout1 += 1;
}
}
}
for &node in &inneis_2[c2] {
if end {
break;
}
let nu = node as usize;
if core_2[nu] >= 0 {
let node2 = core_2[nu];
if !contains_sorted(&inneis_1[c1], node2 as u32) {
end = true;
} else if edge_color1.is_some() {
let eid1 = graph1.get_eid(node2 as u32, cand1 as u32)? as usize;
let eid2 = graph2.get_eid(node, cand2 as u32)? as usize;
if let (Some(ec1), Some(ec2)) = (edge_color1, edge_color2) {
if ec1[eid1] != ec2[eid2] {
end = true;
}
}
}
} else {
if in_2[nu] != 0 {
xin2 += 1;
}
if out_2[nu] != 0 {
xout2 += 1;
}
}
}
for &node in &outneis_2[c2] {
if end {
break;
}
let nu = node as usize;
if core_2[nu] >= 0 {
let node2 = core_2[nu];
if !contains_sorted(&outneis_1[c1], node2 as u32) {
end = true;
} else if edge_color1.is_some() {
let eid1 = graph1.get_eid(cand1 as u32, node2 as u32)? as usize;
let eid2 = graph2.get_eid(cand2 as u32, node)? as usize;
if let (Some(ec1), Some(ec2)) = (edge_color1, edge_color2) {
if ec1[eid1] != ec2[eid2] {
end = true;
}
}
}
} else {
if in_2[nu] != 0 {
xin2 += 1;
}
if out_2[nu] != 0 {
xout2 += 1;
}
}
}
if !end && xin1 >= xin2 && xout1 >= xout2 {
depth += 1;
path.push(cand1);
path.push(cand2);
matched_nodes += 1;
core_1[c1] = cand2;
core_2[c2] = cand1;
if in_1[c1] != 0 {
in_1_size -= 1;
}
if out_1[c1] != 0 {
out_1_size -= 1;
}
if in_2[c2] != 0 {
in_2_size -= 1;
}
if out_2[c2] != 0 {
out_2_size -= 1;
}
for &node in &inneis_1[c1] {
let nu = node as usize;
if in_1[nu] == 0 && core_1[nu] < 0 {
in_1[nu] = depth;
in_1_size += 1;
}
}
for &node in &outneis_1[c1] {
let nu = node as usize;
if out_1[nu] == 0 && core_1[nu] < 0 {
out_1[nu] = depth;
out_1_size += 1;
}
}
for &node in &inneis_2[c2] {
let nu = node as usize;
if in_2[nu] == 0 && core_2[nu] < 0 {
in_2[nu] = depth;
in_2_size += 1;
}
}
for &node in &outneis_2[c2] {
let nu = node as usize;
if out_2[nu] == 0 && core_2[nu] < 0 {
out_2[nu] = depth;
out_2_size += 1;
}
}
last1 = -1;
last2 = -1;
} else {
last1 = cand1;
last2 = cand2;
}
}
if matched_nodes == no_of_nodes2 {
if let Flow::Stop = isohandler(&core_1, &core_2) {
break;
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn subisomorphic_vf2(
graph1: &Graph,
graph2: &Graph,
vertex_color1: Option<&[u32]>,
vertex_color2: Option<&[u32]>,
edge_color1: Option<&[u32]>,
edge_color2: Option<&[u32]>,
) -> IgraphResult<Vf2Subisomorphism> {
let mut map21: Vec<u32> = Vec::new();
let mut map12: Vec<Option<u32>> = Vec::new();
let mut iso = false;
subiso_engine(
graph1,
graph2,
vertex_color1,
vertex_color2,
edge_color1,
edge_color2,
|core_1, core_2| {
iso = true;
map21 = core_2.iter().map(|&x| x as u32).collect();
map12 = core_1
.iter()
.map(|&x| if x < 0 { None } else { Some(x as u32) })
.collect();
Flow::Stop
},
)?;
if !iso {
map21.clear();
map12.clear();
}
Ok(Vf2Subisomorphism { iso, map21, map12 })
}
#[allow(clippy::too_many_arguments)]
pub fn count_subisomorphisms_vf2(
graph1: &Graph,
graph2: &Graph,
vertex_color1: Option<&[u32]>,
vertex_color2: Option<&[u32]>,
edge_color1: Option<&[u32]>,
edge_color2: Option<&[u32]>,
) -> IgraphResult<u64> {
let mut count = 0u64;
subiso_engine(
graph1,
graph2,
vertex_color1,
vertex_color2,
edge_color1,
edge_color2,
|_core_1, _core_2| {
count += 1;
Flow::Continue
},
)?;
Ok(count)
}
#[allow(clippy::too_many_arguments)]
pub fn get_subisomorphisms_vf2(
graph1: &Graph,
graph2: &Graph,
vertex_color1: Option<&[u32]>,
vertex_color2: Option<&[u32]>,
edge_color1: Option<&[u32]>,
edge_color2: Option<&[u32]>,
) -> IgraphResult<Vec<Vec<u32>>> {
let mut maps: Vec<Vec<u32>> = Vec::new();
subiso_engine(
graph1,
graph2,
vertex_color1,
vertex_color2,
edge_color1,
edge_color2,
|_core_1, core_2| {
maps.push(core_2.iter().map(|&x| x as u32).collect());
Flow::Continue
},
)?;
Ok(maps)
}
#[cfg(test)]
mod tests {
use super::*;
fn ring(n: u32, directed: bool) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for i in 0..n {
g.add_edge(i, (i + 1) % n).expect("edge");
}
g
}
fn graph_from(n: u32, directed: bool, edges: &[(u32, u32)]) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for &(u, v) in edges {
g.add_edge(u, v).expect("edge");
}
g
}
fn complete(n: u32) -> Graph {
let mut g = Graph::new(n, false).expect("graph");
for i in 0..n {
for j in (i + 1)..n {
g.add_edge(i, j).expect("edge");
}
}
g
}
fn triangle() -> Graph {
graph_from(3, false, &[(0, 1), (1, 2), (2, 0)])
}
#[test]
fn triangle_embeds_into_k4() {
let k4 = complete(4);
let tri = triangle();
let r = subisomorphic_vf2(&k4, &tri, None, None, None, None).expect("ok");
assert!(r.iso);
assert_eq!(r.map21.len(), 3);
for e in 0..tri.ecount() {
let eid = u32::try_from(e).expect("fits");
let (u, v) = tri.edge(eid).expect("edge");
let mu = r.map21[u as usize];
let mv = r.map21[v as usize];
assert!(k4.find_eid(mu, mv).expect("lookup").is_some());
}
for (j, &t) in r.map21.iter().enumerate() {
assert_eq!(r.map12[t as usize], Some(j as u32));
}
}
#[test]
fn triangle_count_into_k4_and_k5() {
assert_eq!(
count_subisomorphisms_vf2(&complete(4), &triangle(), None, None, None, None)
.expect("ok"),
24
);
assert_eq!(
count_subisomorphisms_vf2(&complete(5), &triangle(), None, None, None, None)
.expect("ok"),
60
);
}
#[test]
fn edge_count_into_triangle_and_path() {
let edge = graph_from(2, false, &[(0, 1)]);
assert_eq!(
count_subisomorphisms_vf2(&triangle(), &edge, None, None, None, None).expect("ok"),
6
);
let p4 = graph_from(4, false, &[(0, 1), (1, 2), (2, 3)]);
assert_eq!(
count_subisomorphisms_vf2(&p4, &edge, None, None, None, None).expect("ok"),
6
);
}
#[test]
fn path_embeds_into_cycle() {
let c4 = ring(4, false);
let p3 = graph_from(3, false, &[(0, 1), (1, 2)]);
let maps = get_subisomorphisms_vf2(&c4, &p3, None, None, None, None).expect("ok");
assert_eq!(maps.len(), 8);
let c = count_subisomorphisms_vf2(&c4, &p3, None, None, None, None).expect("ok");
assert_eq!(c, 8);
}
#[test]
fn triangle_not_in_cycle() {
let c4 = ring(4, false);
let r = subisomorphic_vf2(&c4, &triangle(), None, None, None, None).expect("ok");
assert!(!r.iso);
assert!(r.map21.is_empty());
assert!(r.map12.is_empty());
assert_eq!(
count_subisomorphisms_vf2(&c4, &triangle(), None, None, None, None).expect("ok"),
0
);
}
#[test]
fn pattern_larger_than_target_has_no_embedding() {
let tri = triangle();
let edge = graph_from(2, false, &[(0, 1)]);
let r = subisomorphic_vf2(&edge, &tri, None, None, None, None).expect("ok");
assert!(!r.iso);
assert_eq!(
count_subisomorphisms_vf2(&edge, &tri, None, None, None, None).expect("ok"),
0
);
let path = graph_from(3, false, &[(0, 1), (1, 2)]);
assert_eq!(
count_subisomorphisms_vf2(&path, &triangle(), None, None, None, None).expect("ok"),
0
);
}
#[test]
fn self_subiso_equals_automorphism_count() {
assert_eq!(
count_subisomorphisms_vf2(&ring(6, false), &ring(6, false), None, None, None, None)
.expect("ok"),
12
);
assert_eq!(
count_subisomorphisms_vf2(&ring(4, false), &ring(4, false), None, None, None, None)
.expect("ok"),
8
);
assert_eq!(
count_subisomorphisms_vf2(&ring(4, true), &ring(4, true), None, None, None, None)
.expect("ok"),
4
);
}
#[test]
fn empty_pattern_embeds_once() {
let empty = Graph::new(0, false).expect("graph");
let tri = triangle();
let r = subisomorphic_vf2(&tri, &empty, None, None, None, None).expect("ok");
assert!(r.iso);
assert!(r.map21.is_empty());
assert_eq!(
count_subisomorphisms_vf2(&tri, &empty, None, None, None, None).expect("ok"),
1
);
}
#[test]
fn directed_pattern_respects_orientation() {
let dtri = ring(3, true);
let dpath = graph_from(3, true, &[(0, 1), (1, 2)]);
assert_eq!(
count_subisomorphisms_vf2(&dtri, &dpath, None, None, None, None).expect("ok"),
3
);
}
#[test]
fn vertex_colors_constrain_embedding() {
let tri = triangle();
let tcolors = [0u32, 1, 2];
let edge = graph_from(2, false, &[(0, 1)]);
let pcolors = [0u32, 1];
let c = count_subisomorphisms_vf2(&tri, &edge, Some(&tcolors), Some(&pcolors), None, None)
.expect("ok");
assert_eq!(c, 1);
}
#[test]
fn only_one_side_colored_ignores_colors() {
let tri = triangle();
let tcolors = [0u32, 1, 2];
let edge = graph_from(2, false, &[(0, 1)]);
let c =
count_subisomorphisms_vf2(&tri, &edge, Some(&tcolors), None, None, None).expect("ok");
assert_eq!(c, 6);
}
#[test]
fn edge_colors_constrain_embedding() {
let tri = triangle();
let mut tec = vec![0u32; tri.ecount()];
for (e, slot) in tec.iter_mut().enumerate() {
let eid = u32::try_from(e).expect("fits");
let (a, b) = tri.edge(eid).expect("edge");
if (a, b) == (1, 2) || (a, b) == (2, 1) {
*slot = 1;
}
}
let edge = graph_from(2, false, &[(0, 1)]);
let pec = [1u32];
let c =
count_subisomorphisms_vf2(&tri, &edge, None, None, Some(&tec), Some(&pec)).expect("ok");
assert_eq!(c, 2);
}
#[test]
fn get_and_count_agree() {
let k4 = complete(4);
let tri = triangle();
let maps = get_subisomorphisms_vf2(&k4, &tri, None, None, None, None).expect("ok");
let c = count_subisomorphisms_vf2(&k4, &tri, None, None, None, None).expect("ok");
assert_eq!(maps.len() as u64, c);
for m in &maps {
for e in 0..tri.ecount() {
let eid = u32::try_from(e).expect("fits");
let (u, v) = tri.edge(eid).expect("edge");
assert!(
k4.find_eid(m[u as usize], m[v as usize])
.expect("lookup")
.is_some()
);
}
}
}
#[test]
fn self_loops_are_rejected() {
let g = graph_from(2, false, &[(0, 0), (0, 1)]);
let h = graph_from(2, false, &[(0, 1)]);
assert!(subisomorphic_vf2(&g, &h, None, None, None, None).is_err());
assert!(subisomorphic_vf2(&h, &g, None, None, None, None).is_err());
}
#[test]
fn directedness_mismatch_is_rejected() {
let u = ring(3, false);
let d = ring(3, true);
assert!(subisomorphic_vf2(&u, &d, None, None, None, None).is_err());
}
#[test]
fn wrong_color_vector_length_errors() {
let tri = triangle();
let edge = graph_from(2, false, &[(0, 1)]);
let short = [0u32];
assert!(
subisomorphic_vf2(&tri, &edge, Some(&short), Some(&[0u32, 0]), None, None).is_err()
);
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptests {
use super::*;
use proptest::prelude::*;
use std::collections::HashSet;
fn arb_simple_graph(max_v: u32) -> impl Strategy<Value = (u32, Vec<(u32, u32)>, bool)> {
(1..=max_v, any::<bool>()).prop_flat_map(|(n, directed)| {
proptest::collection::vec((0..n, 0..n), 0..=12).prop_map(move |raw| {
let mut seen: HashSet<(u32, u32)> = HashSet::new();
let mut edges = Vec::new();
for (u, v) in raw {
if u == v {
continue;
}
let key = if directed {
(u, v)
} else {
(u.min(v), u.max(v))
};
if seen.insert(key) {
edges.push((u, v));
}
}
(n, edges, directed)
})
})
}
fn relabel(edges: &[(u32, u32)], perm: &[u32]) -> Vec<(u32, u32)> {
edges
.iter()
.map(|&(u, v)| (perm[u as usize], perm[v as usize]))
.collect()
}
fn build(n: u32, directed: bool, edges: &[(u32, u32)]) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for &(u, v) in edges {
g.add_edge(u, v).expect("edge");
}
g
}
proptest! {
#[test]
fn self_embeds((n, edges, directed) in arb_simple_graph(6)) {
let g = build(n, directed, &edges);
let r = subisomorphic_vf2(&g, &g, None, None, None, None).expect("ok");
prop_assert!(r.iso);
prop_assert_eq!(r.map21.len(), n as usize);
let c = count_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
prop_assert!(c >= 1, "identity embedding always exists");
}
#[test]
fn get_count_consistency_and_adjacency(
(n, edges, directed) in arb_simple_graph(6),
) {
let g = build(n, directed, &edges);
let maps = get_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
let c = count_subisomorphisms_vf2(&g, &g, None, None, None, None).expect("ok");
prop_assert_eq!(maps.len() as u64, c);
for m in &maps {
prop_assert_eq!(m.len(), n as usize);
for e in 0..g.ecount() {
let eid = u32::try_from(e).expect("fits");
let (u, v) = g.edge(eid).expect("edge");
prop_assert!(
g.find_eid(m[u as usize], m[v as usize]).expect("lookup").is_some()
);
}
}
}
#[test]
fn count_invariant_under_target_relabelling(
(n, edges, directed) in arb_simple_graph(6),
seed in any::<u64>(),
) {
let mut perm: Vec<u32> = (0..n).collect();
let mut state = seed;
for i in (1..n as usize).rev() {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let j = (state >> 33) as usize % (i + 1);
perm.swap(i, j);
}
let g = build(n, directed, &edges);
let h = build(n, directed, &relabel(&edges, &perm));
let pattern = build(2, directed, &[(0, 1)]);
let cg = count_subisomorphisms_vf2(&g, &pattern, None, None, None, None).expect("ok");
let ch = count_subisomorphisms_vf2(&h, &pattern, None, None, None, None).expect("ok");
prop_assert_eq!(cg, ch);
}
#[test]
fn oversized_pattern_never_embeds(
(n, edges, directed) in arb_simple_graph(5),
) {
let target = build(n, directed, &edges);
let bigger = build(n + 1, directed, &edges);
let c = count_subisomorphisms_vf2(&target, &bigger, None, None, None, None)
.expect("ok");
prop_assert_eq!(c, 0);
}
}
}