diff --git a/src/undirected_graph.rs b/src/undirected_graph.rs index 437b1bc3cb9dff1abc2eed942e9066dc08e7ac69..fd8f81e238d2facedc1ee246a6b936019572740a 100644 --- a/src/undirected_graph.rs +++ b/src/undirected_graph.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use union_find::{QuickUnionUf, UnionByRank, UnionFind}; #[derive(Debug, Eq, Hash, PartialEq, Clone)] pub struct UndirectedGraph { @@ -6,7 +7,30 @@ pub struct UndirectedGraph { edges: Vec<(usize, usize)>, } +struct EdgeOrbitData { + graph: UndirectedGraph, + graph_is_odd: bool, + compute_edge_orbits: bool, + // Union-Find for edge orbits. + edge_orbits_uf: QuickUnionUf<UnionByRank>, +} + +impl EdgeOrbitData { + pub fn new() -> Self { + EdgeOrbitData { + graph: UndirectedGraph::new(0, vec![]), + graph_is_odd: false, + compute_edge_orbits: false, + edge_orbits_uf: QuickUnionUf::<UnionByRank>::new(0), + } + } +} + impl UndirectedGraph { + thread_local! { + static EDGE_ORBIT_DATA: std::cell::RefCell<EdgeOrbitData> = std::cell::RefCell::new(EdgeOrbitData::new()); + } + pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self { let mut edges_seen: HashSet<(usize, usize)> = HashSet::new(); for (a, b) in &edges { @@ -78,7 +102,11 @@ impl UndirectedGraph { fn map_edge(relabel_h_to_g: &[i32], e: &(usize, usize)) -> (usize, usize) { let a: usize = relabel_h_to_g[e.0] as usize; let b: usize = relabel_h_to_g[e.1] as usize; - if a < b { (a, b) } else { (b, a) } + if a < b { + (a, b) + } else { + (b, a) + } } let mapped_edges: Vec<(usize, usize)> = h .edges @@ -108,12 +136,8 @@ impl UndirectedGraph { sign } - thread_local! { - static AUTOMORPHSM_GROUP_GENERATORS: std::cell::RefCell<Vec<Vec<i32>>> = const { std::cell::RefCell::new(vec![]) }; - } - #[no_mangle] - extern "C" fn store_nauty_automorphism( + extern "C" fn process_nauty_automorphism( _count: i32, perm: *mut i32, _orbits: *mut i32, @@ -123,7 +147,37 @@ impl UndirectedGraph { ) { unsafe { let automorphism: Vec<i32> = std::slice::from_raw_parts(perm, n as usize).to_vec(); - Self::AUTOMORPHSM_GROUP_GENERATORS.with_borrow_mut(|gens| gens.push(automorphism)); + + Self::EDGE_ORBIT_DATA.with_borrow_mut(|edge_orbit_data: &mut EdgeOrbitData| { + // NOTE: The automorphisms produced by nauty act on the original graph. + let sigma: Vec<i32> = Self::induced_edge_permutation( + &edge_orbit_data.graph, + &edge_orbit_data.graph, + &automorphism, + ); + + if edge_orbit_data.compute_edge_orbits { + // Take powers of sigma and act on edges of graph. + let mut tau: Vec<i32> = sigma.clone(); + let num_edges: i32 = edge_orbit_data.graph.edges.len() as i32; + let identity_permutation: Vec<i32> = (0..num_edges).collect(); + while tau != identity_permutation { + for (e, &tau_e) in tau.iter().enumerate() { + edge_orbit_data.edge_orbits_uf.union(e, tau_e as usize); + } + for e in tau.iter_mut() { + *e = sigma[*e as usize]; + } + } + } + + if Self::permutation_sign(sigma) == -1 { + edge_orbit_data.graph_is_odd = true; + // NOTE: Giving up computation of edge orbits; we will not use them anyway. + edge_orbit_data.compute_edge_orbits = false; + // NOTE: There is no way to make nauty cancel the computation here. + } + }); } } @@ -135,7 +189,7 @@ impl UndirectedGraph { let mut options: nauty_Traces_sys::optionblk = nauty_Traces_sys::optionblk { getcanon: nauty_Traces_sys::TRUE, - userautomproc: Some(Self::store_nauty_automorphism), + userautomproc: Some(Self::process_nauty_automorphism), ..Default::default() }; let mut stats: nauty_Traces_sys::statsblk = nauty_Traces_sys::statsblk::default(); @@ -149,7 +203,12 @@ impl UndirectedGraph { let mut normal_graph: Vec<u64> = vec![0; n]; - Self::AUTOMORPHSM_GROUP_GENERATORS.set(vec![]); + Self::EDGE_ORBIT_DATA.set(EdgeOrbitData { + graph: self.clone(), + graph_is_odd: false, + compute_edge_orbits, + edge_orbits_uf: QuickUnionUf::<UnionByRank>::new(self.edges.len()), + }); unsafe { nauty_Traces_sys::densenauty( @@ -186,43 +245,17 @@ impl UndirectedGraph { let mut sign: i32 = 1; let mut normal_self_edge_orbits: Vec<usize> = vec![]; - Self::AUTOMORPHSM_GROUP_GENERATORS.with_borrow(|gens| { - // NOTE: Use Union-Find to compute edge orbits. - use union_find::{QuickUnionUf, UnionByRank, UnionFind}; - let mut uf: QuickUnionUf<UnionByRank> = - QuickUnionUf::<UnionByRank>::new(self.edges.len()); - - for automorphism in gens { - // NOTE: The automorphisms produced by nauty act on the original graph. - let sigma: Vec<i32> = Self::induced_edge_permutation(self, self, automorphism); - - if compute_edge_orbits { - // Take powers of sigma and act on edges of self. - let mut tau: Vec<i32> = sigma.clone(); - let num_edges: i32 = self.edges.len() as i32; - let identity_permutation: Vec<i32> = (0..num_edges).collect(); - while tau != identity_permutation { - for (e, &tau_e) in tau.iter().enumerate() { - uf.union(e, tau_e as usize); - } - for e in tau.iter_mut() { - *e = sigma[*e as usize]; - } - } - } - - if Self::permutation_sign(sigma) == -1 { - sign = 0; - // NOTE: Giving up computation of edge orbits; we will not use them anyway. - break; - } + Self::EDGE_ORBIT_DATA.with_borrow_mut(|edge_orbit_data: &mut EdgeOrbitData| { + if edge_orbit_data.graph_is_odd { + sign = 0; } - if sign != 0 { let edge_permutation: Vec<i32> = Self::induced_edge_permutation(&normal_self, self, &lab_inv); - if compute_edge_orbits { - normal_self_edge_orbits = (0..self.edges.len()).map(|e| uf.find(e)).collect(); + if edge_orbit_data.compute_edge_orbits { + normal_self_edge_orbits = (0..self.edges.len()) + .map(|e| edge_orbit_data.edge_orbits_uf.find(e)) + .collect(); // So far these are the edge orbits of self. Finally we apply the isomorphism to get the edge orbits of normal_self. for e in &mut normal_self_edge_orbits { *e = edge_permutation[*e] as usize;