1use std::collections::HashMap;
23use std::fmt::Debug;
24use std::hash::Hash;
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
32struct Edge<E: Clone + Debug + Eq + Hash + Ord> {
33 from: usize,
34 to: usize,
35 directed: bool,
36 data: E,
37}
38
39#[derive(Debug, Clone)]
46pub struct Graph<N, H, E>
47where
48 N: Clone + Debug + Eq + Hash + Ord,
49 H: Clone + Debug,
50 E: Clone + Debug + Eq + Hash + Ord,
51{
52 nodes: Vec<(N, H)>,
53 edges: Vec<Edge<E>>,
54 adj_out: Vec<Vec<(usize, usize)>>,
58 adj_in: Vec<Vec<(usize, usize)>>,
61}
62
63#[derive(Debug, Clone)]
65pub struct CanonicalForm<N, H, E>
66where
67 N: Clone + Debug + Eq + Hash + Ord,
68 H: Clone + Debug,
69 E: Clone + Debug + Eq + Hash + Ord,
70{
71 pub vertex_map: Vec<usize>,
73 pub orbits: Vec<Vec<usize>>,
76 pub automorphism_group_size: u64,
79 pub graph: Graph<N, H, E>,
81}
82
83impl<N, H, E> Graph<N, H, E>
88where
89 N: Clone + Debug + Eq + Hash + Ord,
90 H: Clone + Debug,
91 E: Clone + Debug + Eq + Hash + Ord,
92{
93 pub fn new() -> Self {
95 Self {
96 nodes: Vec::new(),
97 edges: Vec::new(),
98 adj_out: Vec::new(),
99 adj_in: Vec::new(),
100 }
101 }
102
103 pub fn add_node(&mut self, data: N, hidden: H) -> usize {
106 let idx = self.nodes.len();
107 self.nodes.push((data, hidden));
108 self.adj_out.push(Vec::new());
109 self.adj_in.push(Vec::new());
110 idx
111 }
112
113 pub fn node_count(&self) -> usize {
115 self.nodes.len()
116 }
117
118 pub fn node_data(&self, v: usize) -> &N {
120 &self.nodes[v].0
121 }
122
123 pub fn node_hidden(&self, v: usize) -> &H {
125 &self.nodes[v].1
126 }
127
128 pub fn add_directed_edge(&mut self, from: usize, to: usize, data: E) {
130 let idx = self.edges.len();
131 self.edges.push(Edge {
132 from,
133 to,
134 directed: true,
135 data,
136 });
137 self.adj_out[from].push((idx, to));
138 self.adj_in[to].push((idx, from));
139 }
140
141 pub fn add_undirected_edge(&mut self, u: usize, v: usize, data: E) {
143 let idx = self.edges.len();
144 self.edges.push(Edge {
145 from: u,
146 to: v,
147 directed: false,
148 data,
149 });
150 self.adj_out[u].push((idx, v));
151 self.adj_out[v].push((idx, u));
152 }
153
154 pub fn edges_of(&self, v: usize) -> EdgeIter<'_, N, H, E> {
157 EdgeIter {
158 graph: self,
159 v,
160 out_pos: 0,
161 in_pos: 0,
162 }
163 }
164
165 #[allow(clippy::needless_range_loop)]
171 pub fn canonize(&self) -> CanonicalForm<N, H, E> {
172 let n = self.nodes.len();
173 if n == 0 {
174 return CanonicalForm {
175 vertex_map: Vec::new(),
176 orbits: Vec::new(),
177 automorphism_group_size: 1,
178 graph: self.clone(),
179 };
180 }
181
182 let mut initial = Partition::from_graph(self);
184 initial.refine(self);
185
186 let mut stack: Vec<SearchFrame> = Vec::new();
188 let root_inv = Invariant::from_partition(&initial, n);
189
190 stack.push(SearchFrame {
191 partition: initial,
192 invariant: root_inv,
193 is_leftmost: true,
194 });
195
196 let mut best_labeling: Option<Vec<usize>> = None;
198 let mut best_cert: Option<Vec<(usize, usize, E, u8)>> = None;
199 let mut orbit_generators: Vec<Vec<usize>> = Vec::new();
201 let mut leaf_seen: HashMap<u64, Vec<usize>> = HashMap::new();
203
204 while let Some(frame) = stack.pop() {
205 if frame.partition.is_discrete() {
206 let labelling: Vec<usize> = frame.partition.labeling();
207 let cert = self.certificate(&labelling);
208
209 match best_cert.as_ref() {
210 None => {
211 best_labeling = Some(labelling.clone());
212 best_cert = Some(cert);
213 leaf_seen.clear();
214 leaf_seen.insert(hash_slice(best_cert.as_ref().unwrap()), labelling);
215 }
216 Some(prev) if cert > *prev => {
217 best_labeling = Some(labelling.clone());
218 best_cert = Some(cert);
219 orbit_generators.clear();
220 leaf_seen.clear();
221 leaf_seen.insert(hash_slice(best_cert.as_ref().unwrap()), labelling);
222 }
223 Some(prev) if cert == *prev => {
224 if let Some(ref best_lab) = best_labeling {
225 let autom = compose_permutations(&labelling, best_lab);
226 orbit_generators.push(autom);
227 }
228 }
229 _ => {}
230 }
231 continue;
232 }
233
234 let cell_idx = match frame.partition.smallest_nontrivial_cell() {
236 Some(ci) => ci,
237 None => continue,
238 };
239 let cell: Vec<usize> = frame.partition.cells[cell_idx].clone();
240 let cell_len = cell.len();
241
242 let mut children: Vec<SearchFrame> = Vec::new();
244 let mut processed_siblings: Vec<usize> = Vec::new();
245
246 for pos in 0..cell_len {
247 let v = cell[pos];
248
249 if orbit_prune(
252 v,
253 &processed_siblings,
254 &orbit_generators,
255 cell_idx,
256 &frame.partition,
257 ) {
258 continue;
259 }
260
261 let mut child_part = frame.partition.clone();
262 child_part.individualize(cell_idx, pos);
263 child_part.refine(self);
264
265 let child_inv = frame.invariant.extend(child_part.cell_lengths());
266
267 if let Some(ref _best_cert) = best_cert
269 && best_labeling.is_some()
270 && !frame.is_leftmost
271 {
272 }
275
276 let is_left = frame.is_leftmost && pos == 0;
277
278 children.push(SearchFrame {
279 partition: child_part,
280 invariant: child_inv,
281 is_leftmost: is_left,
282 });
283
284 processed_siblings.push(v);
285 }
286
287 for child in children.into_iter().rev() {
289 stack.push(child);
290 }
291 }
292
293 let autom_count = if orbit_generators.is_empty() {
295 1
296 } else {
297 group_size(&orbit_generators, n)
298 };
299
300 let labeling = best_labeling.unwrap_or_else(|| (0..n).collect());
302 let orbits = compute_orbits(&orbit_generators, n);
303 let canon_graph = self.relabeled(&labeling);
304
305 CanonicalForm {
306 vertex_map: labeling,
307 orbits,
308 automorphism_group_size: autom_count,
309 graph: canon_graph,
310 }
311 }
312
313 fn certificate(&self, labeling: &[usize]) -> Vec<(usize, usize, E, u8)> {
321 let n = labeling.len();
322 let mut pos_of = vec![0usize; n];
323 for (pos, &v) in labeling.iter().enumerate() {
324 pos_of[v] = pos;
325 }
326
327 let mut cert: Vec<(usize, usize, E, u8)> = Vec::new();
328
329 for (v, out_list) in self.adj_out.iter().enumerate() {
331 let i = pos_of[v];
332 for &(edge_idx, other) in out_list {
333 let edge = &self.edges[edge_idx];
334 let j = pos_of[other];
335 let dir = if edge.directed { 0u8 } else { 2u8 };
336 cert.push((i, j, edge.data.clone(), dir));
337 }
338 }
339
340 for (v, in_list) in self.adj_in.iter().enumerate() {
342 let j = pos_of[v]; for &(edge_idx, other) in in_list {
344 let edge = &self.edges[edge_idx];
345 let i = pos_of[other]; cert.push((i, j, edge.data.clone(), 1u8));
347 }
348 }
349
350 cert.sort();
351 cert
352 }
353
354 fn relabeled(&self, labeling: &[usize]) -> Graph<N, H, E> {
356 let n = labeling.len();
357 let mut new_nodes = vec![(self.nodes[0].0.clone(), self.nodes[0].1.clone()); n];
358 for (new_idx, &old_idx) in labeling.iter().enumerate() {
359 new_nodes[new_idx] = self.nodes[old_idx].clone();
360 }
361
362 let mut new_graph = Graph {
363 nodes: new_nodes,
364 edges: Vec::new(),
365 adj_out: vec![Vec::new(); n],
366 adj_in: vec![Vec::new(); n],
367 };
368
369 let mut new_of = vec![0usize; self.nodes.len()];
371 for (new_idx, &old_idx) in labeling.iter().enumerate() {
372 new_of[old_idx] = new_idx;
373 }
374
375 for edge in &self.edges {
376 let new_from = new_of[edge.from];
377 let new_to = new_of[edge.to];
378 if edge.directed {
379 new_graph.add_directed_edge(new_from, new_to, edge.data.clone());
380 } else {
381 new_graph.add_undirected_edge(new_from, new_to, edge.data.clone());
382 }
383 }
384
385 new_graph
386 }
387}
388
389impl<N, H, E> Default for Graph<N, H, E>
390where
391 N: Clone + Debug + Eq + Hash + Ord,
392 H: Clone + Debug,
393 E: Clone + Debug + Eq + Hash + Ord,
394{
395 fn default() -> Self {
396 Self::new()
397 }
398}
399
400pub struct EdgeIter<'a, N, H, E>
402where
403 N: Clone + Debug + Eq + Hash + Ord,
404 H: Clone + Debug,
405 E: Clone + Debug + Eq + Hash + Ord,
406{
407 graph: &'a Graph<N, H, E>,
408 v: usize,
409 out_pos: usize,
410 in_pos: usize,
411}
412
413#[derive(Debug, Clone)]
415pub struct EdgeView<E: Clone> {
416 pub data: E,
417 pub neighbour: usize,
418 pub is_directed: bool,
419 pub is_outgoing: bool,
420}
421
422impl<'a, N, H, E> Iterator for EdgeIter<'a, N, H, E>
423where
424 N: Clone + Debug + Eq + Hash + Ord,
425 H: Clone + Debug,
426 E: Clone + Debug + Eq + Hash + Ord,
427{
428 type Item = EdgeView<E>;
429
430 fn next(&mut self) -> Option<Self::Item> {
431 loop {
432 if self.out_pos < self.graph.adj_out[self.v].len() {
433 let (ei, nb) = self.graph.adj_out[self.v][self.out_pos];
434 self.out_pos += 1;
435 let edge = &self.graph.edges[ei];
436 if edge.directed && edge.to == self.v {
437 continue;
438 }
439 return Some(EdgeView {
440 data: edge.data.clone(),
441 neighbour: nb,
442 is_directed: edge.directed,
443 is_outgoing: true,
444 });
445 }
446 if self.in_pos < self.graph.adj_in[self.v].len() {
447 let (ei, nb) = self.graph.adj_in[self.v][self.in_pos];
448 self.in_pos += 1;
449 return Some(EdgeView {
450 data: self.graph.edges[ei].data.clone(),
451 neighbour: nb,
452 is_directed: true,
453 is_outgoing: false,
454 });
455 }
456 return None;
457 }
458 }
459}
460
461fn hash_slice<E: Hash>(slice: &[E]) -> u64 {
466 use std::hash::Hasher;
467 let mut h = std::collections::hash_map::DefaultHasher::new();
468 slice.hash(&mut h);
469 h.finish()
470}
471
472fn group_size(generators: &[Vec<usize>], n: usize) -> u64 {
475 let mut seen: std::collections::HashSet<Vec<usize>> = std::collections::HashSet::new();
476 let mut queue: Vec<Vec<usize>> = Vec::new();
477
478 let id: Vec<usize> = (0..n).collect();
480 seen.insert(id.clone());
481 queue.push(id);
482
483 let inverses: Vec<Vec<usize>> = generators
485 .iter()
486 .map(|g| {
487 let mut inv = vec![0usize; n];
488 for (i, &img) in g.iter().enumerate() {
489 inv[img] = i;
490 }
491 inv
492 })
493 .collect();
494
495 let mut head = 0;
496 while head < queue.len() {
497 let cur = queue[head].clone();
498 head += 1;
499
500 for generator in generators {
501 let next: Vec<usize> = cur.iter().map(|&v| generator[v]).collect();
502 if seen.insert(next.clone()) {
503 queue.push(next);
504 }
505 }
506 for inv in &inverses {
507 let prev: Vec<usize> = cur.iter().map(|&v| inv[v]).collect();
508 if seen.insert(prev.clone()) {
509 queue.push(prev);
510 }
511 }
512 }
513
514 seen.len() as u64
515}
516
517#[derive(Debug, Clone)]
526struct Partition {
527 cells: Vec<Vec<usize>>,
528 cell_of: Vec<usize>,
530}
531
532impl Partition {
533 fn from_graph<N, H, E>(g: &Graph<N, H, E>) -> Self
535 where
536 N: Clone + Debug + Eq + Hash + Ord,
537 H: Clone + Debug,
538 E: Clone + Debug + Eq + Hash + Ord,
539 {
540 let n = g.nodes.len();
541 let mut groups: HashMap<N, Vec<usize>> = HashMap::new();
543 for (v, (data, _)) in g.nodes.iter().enumerate() {
544 groups.entry(data.clone()).or_default().push(v);
545 }
546 let mut cells: Vec<(N, Vec<usize>)> = groups.into_iter().collect();
548 cells.sort_by(|a, b| a.0.cmp(&b.0));
549 let cells: Vec<Vec<usize>> = cells
550 .into_iter()
551 .map(|(_, mut vs)| {
552 vs.sort();
553 vs
554 })
555 .collect();
556
557 let mut cell_of = vec![0usize; n];
558 for (ci, cell) in cells.iter().enumerate() {
559 for &v in cell {
560 cell_of[v] = ci;
561 }
562 }
563
564 Partition { cells, cell_of }
565 }
566
567 fn cell_lengths(&self) -> Vec<usize> {
568 self.cells.iter().map(|c| c.len()).collect()
569 }
570
571 fn is_discrete(&self) -> bool {
572 self.cells.iter().all(|c| c.len() == 1)
573 }
574
575 fn labeling(&self) -> Vec<usize> {
576 self.cells.iter().flatten().copied().collect()
578 }
579
580 fn smallest_nontrivial_cell(&self) -> Option<usize> {
581 let mut best: Option<(usize, usize)> = None; for (i, cell) in self.cells.iter().enumerate() {
583 if cell.len() <= 1 {
584 continue;
585 }
586 match best {
587 None => best = Some((i, cell.len())),
588 Some((_, best_len)) if cell.len() < best_len => {
589 best = Some((i, cell.len()));
590 }
591 _ => {}
592 }
593 }
594 best.map(|(i, _)| i)
595 }
596
597 fn individualize(&mut self, cell_idx: usize, vpos: usize) {
600 let v = self.cells[cell_idx].remove(vpos);
601 self.cells.insert(cell_idx, vec![v]);
602 self.cell_of[v] = cell_idx;
604 for ci in (cell_idx + 1)..self.cells.len() {
606 for &w in &self.cells[ci] {
607 self.cell_of[w] = ci;
608 }
609 }
610 if self.cells[cell_idx + 1].is_empty() {
612 self.cells.remove(cell_idx + 1);
613 for ci in (cell_idx + 1)..self.cells.len() {
614 for &w in &self.cells[ci] {
615 self.cell_of[w] = ci;
616 }
617 }
618 }
619 }
620
621 fn refine<N, H, E>(&mut self, g: &Graph<N, H, E>)
624 where
625 N: Clone + Debug + Eq + Hash + Ord,
626 H: Clone + Debug,
627 E: Clone + Debug + Eq + Hash + Ord,
628 {
629 let num_cells = self.cells.len();
630 if num_cells <= 1 {
631 return;
632 }
633
634 let mut stable_below = 0usize;
637
638 'outer: while stable_below < self.cells.len() {
639 let mut i = stable_below;
640 while i < self.cells.len() {
641 if self.cells[i].len() <= 1 {
642 i += 1;
643 continue;
644 }
645
646 let mut split = false;
648 for j in 0..self.cells.len() {
649 if i == j {
650 continue;
651 }
652
653 let sigs = cell_signatures(
654 &self.cells[i],
655 j,
656 &self.cell_of,
657 &g.adj_out,
658 &g.adj_in,
659 &g.edges,
660 );
661
662 if sigs.len() > 1 {
663 let new_cells: Vec<Vec<usize>> =
664 sigs.into_iter().map(|(_, vs)| vs).collect();
665 let _old_cell = std::mem::take(&mut self.cells[i]);
666 self.cells.splice(i..=i, new_cells);
667
668 for (offset, cell) in self.cells[i..].iter().enumerate() {
669 for &v in cell {
670 self.cell_of[v] = i + offset;
671 }
672 }
673
674 stable_below = i;
675 split = true;
676 break;
677 }
678 }
679 if !split {
680 i += 1;
681 }
682 if split {
683 continue 'outer;
684 }
685 }
686 break;
687 }
688 }
689}
690
691#[allow(clippy::type_complexity)]
696fn cell_signatures<E: Clone + Debug + Eq + Hash + Ord>(
697 cell: &[usize],
698 target_cell_idx: usize,
699 cell_of: &[usize],
700 adj_out: &[Vec<(usize, usize)>],
701 adj_in: &[Vec<(usize, usize)>],
702 edges: &[Edge<E>],
703) -> Vec<(Vec<(E, u8)>, Vec<usize>)> {
704 let mut groups: Vec<(Vec<(E, u8)>, Vec<usize>)> = Vec::new();
705
706 for &v in cell {
707 let sig = vertex_signature(v, target_cell_idx, cell_of, adj_out, adj_in, edges);
709
710 let pos = groups.iter().position(|(s, _)| *s == sig);
712 match pos {
713 Some(idx) => groups[idx].1.push(v),
714 None => groups.push((sig, vec![v])),
715 }
716 }
717
718 groups
719}
720
721fn vertex_signature<E: Clone + Debug + Eq + Hash + Ord>(
723 v: usize,
724 target_cell_idx: usize,
725 cell_of: &[usize],
726 adj_out: &[Vec<(usize, usize)>],
727 adj_in: &[Vec<(usize, usize)>],
728 edges: &[Edge<E>],
729) -> Vec<(E, u8)> {
730 let mut sig: Vec<(E, u8)> = Vec::new();
731
732 for &(edge_idx, other) in &adj_out[v] {
734 if cell_of[other] == target_cell_idx {
735 let edge = &edges[edge_idx];
736 let dir = if edge.directed {
737 if edge.from == v { 0u8 } else { 1u8 }
738 } else {
739 2u8
740 };
741 sig.push((edge.data.clone(), dir));
742 }
743 }
744
745 for &(edge_idx, other) in &adj_in[v] {
747 if cell_of[other] == target_cell_idx {
748 sig.push((edges[edge_idx].data.clone(), 1u8));
749 }
750 }
751
752 sig.sort();
753 sig
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
762struct Invariant {
763 path: Vec<Vec<usize>>,
764}
765
766impl Invariant {
767 fn from_partition(part: &Partition, _n: usize) -> Self {
768 Invariant {
769 path: vec![part.cell_lengths()],
770 }
771 }
772
773 fn extend(&self, cell_lengths: Vec<usize>) -> Self {
774 let mut path = self.path.clone();
775 path.push(cell_lengths);
776 Invariant { path }
777 }
778}
779
780#[derive(Debug, Clone)]
785struct SearchFrame {
786 partition: Partition,
787 invariant: Invariant,
788 is_leftmost: bool,
790}
791
792fn compose_permutations(labeling_a: &[usize], labeling_b: &[usize]) -> Vec<usize> {
800 let n = labeling_a.len();
801 let mut inv_a = vec![0usize; n];
803 for (pos, &v) in labeling_a.iter().enumerate() {
804 inv_a[v] = pos;
805 }
806 let mut inv_b = vec![0usize; n];
825 for (pos, &v) in labeling_b.iter().enumerate() {
826 inv_b[v] = pos;
827 }
828 let mut perm = vec![0usize; n];
829 for i in 0..n {
830 perm[i] = inv_b[labeling_a[i]];
831 }
832 perm
833}
834
835fn orbit_prune(
838 v: usize,
839 processed: &[usize],
840 generators: &[Vec<usize>],
841 _cell_idx: usize,
842 _partition: &Partition,
843) -> bool {
844 if processed.is_empty() || generators.is_empty() {
845 return false;
846 }
847 let n = if let Some(g1) = generators.first() {
850 g1.len()
851 } else {
852 return false;
853 };
854
855 for &p in processed {
856 let mut orbit = vec![false; n];
858 let mut queue = vec![p];
859 orbit[p] = true;
860 let mut head = 0;
861 while head < queue.len() {
862 let cur = queue[head];
863 head += 1;
864 for generator in generators {
865 let next = generator[cur];
866 if !orbit[next] {
867 orbit[next] = true;
868 queue.push(next);
869 }
870 }
875 }
876
877 if orbit[v] {
878 return true;
879 }
880 }
881
882 false
883}
884
885fn compute_orbits(generators: &[Vec<usize>], n: usize) -> Vec<Vec<usize>> {
887 if generators.is_empty() {
888 return (0..n).map(|i| vec![i]).collect();
890 }
891
892 let inverses: Vec<Vec<usize>> = generators
894 .iter()
895 .map(|generator| {
896 let mut inv = vec![0usize; n];
897 for (i, &img) in generator.iter().enumerate() {
898 inv[img] = i;
899 }
900 inv
901 })
902 .collect();
903
904 let mut visited = vec![false; n];
905 let mut orbits: Vec<Vec<usize>> = Vec::new();
906
907 for start in 0..n {
908 if visited[start] {
909 continue;
910 }
911 let mut orbit: Vec<usize> = Vec::new();
912 let mut queue = vec![start];
913 visited[start] = true;
914
915 while let Some(cur) = queue.pop() {
916 orbit.push(cur);
917 for generator in generators {
918 let next = generator[cur];
919 if !visited[next] {
920 visited[next] = true;
921 queue.push(next);
922 }
923 }
924 for inv in &inverses {
925 let prev = inv[cur];
926 if !visited[prev] {
927 visited[prev] = true;
928 queue.push(prev);
929 }
930 }
931 }
932
933 orbit.sort();
934 orbits.push(orbit);
935 }
936
937 orbits.sort_by_key(|o| o[0]);
938 orbits
939}
940
941#[cfg(test)]
946mod tests {
947 use super::*;
948
949 fn trivial_vertices(n: usize) -> Graph<i32, (), ()> {
951 let mut g = Graph::new();
952 for _ in 0..n {
953 g.add_node(0, ());
954 }
955 g
956 }
957
958 #[test]
959 fn empty_graph() {
960 let g: Graph<i32, (), ()> = Graph::new();
961 let cf = g.canonize();
962 assert!(cf.vertex_map.is_empty());
963 assert_eq!(cf.automorphism_group_size, 1);
964 }
965
966 #[test]
967 fn single_vertex() {
968 let mut g = Graph::<i32, (), ()>::new();
969 g.add_node(42, ());
970 let cf = g.canonize();
971 assert_eq!(cf.vertex_map, vec![0]);
972 assert_eq!(cf.orbits.len(), 1);
973 assert_eq!(cf.automorphism_group_size, 1);
974 }
975
976 #[test]
977 fn two_isolated_vertices_same_colour() {
978 let g = trivial_vertices(2);
979 let cf = g.canonize();
980 assert_eq!(cf.orbits.len(), 1);
982 assert_eq!(cf.orbits[0].len(), 2);
983 assert_eq!(cf.automorphism_group_size, 2);
985 }
986
987 #[test]
988 fn three_isolated_vertices_same_colour() {
989 let g = trivial_vertices(3);
990 let cf = g.canonize();
991 assert_eq!(cf.orbits.len(), 1);
992 assert_eq!(cf.orbits[0].len(), 3);
993 assert_eq!(cf.automorphism_group_size, 6);
995 }
996
997 #[test]
998 fn different_colours_not_swappable() {
999 let mut g = Graph::<i32, (), ()>::new();
1000 g.add_node(1, ());
1001 g.add_node(2, ());
1002 let cf = g.canonize();
1003 assert_eq!(cf.orbits.len(), 2);
1005 assert_eq!(cf.automorphism_group_size, 1);
1006 }
1007
1008 #[test]
1009 fn directed_edge_preserves_direction() {
1010 let mut g = Graph::<i32, (), i32>::new();
1011 let a = g.add_node(0, ());
1012 let b = g.add_node(0, ());
1013 g.add_directed_edge(a, b, 1);
1014 let cf = g.canonize();
1015 assert_eq!(cf.orbits.len(), 2);
1017 assert_eq!(cf.automorphism_group_size, 1);
1018 }
1019
1020 #[test]
1021 fn undirected_edge_makes_vertices_equivalent() {
1022 let mut g = Graph::<i32, (), i32>::new();
1023 let a = g.add_node(0, ());
1024 let b = g.add_node(0, ());
1025 g.add_undirected_edge(a, b, 1);
1026 let cf = g.canonize();
1027 assert_eq!(cf.orbits.len(), 1);
1029 assert!(cf.automorphism_group_size >= 1);
1030 }
1031
1032 #[test]
1033 fn cycle_4_automorphism_d8() {
1034 let mut g = Graph::<i32, (), ()>::new();
1035 let v: Vec<usize> = (0..4).map(|_| g.add_node(0, ())).collect();
1036 g.add_undirected_edge(v[0], v[1], ());
1037 g.add_undirected_edge(v[1], v[2], ());
1038 g.add_undirected_edge(v[2], v[3], ());
1039 g.add_undirected_edge(v[3], v[0], ());
1040 let cf = g.canonize();
1041 let mut sorted: Vec<usize> = cf.vertex_map.clone();
1043 sorted.sort();
1044 assert_eq!(sorted, (0..4).collect::<Vec<_>>());
1045 assert_eq!(cf.graph.node_count(), 4);
1046 }
1047
1048 #[test]
1049 fn relabeling_invariance_proptest_style() {
1050 let mut g = Graph::<i32, (), i32>::new();
1052 let v0 = g.add_node(1, ()); let v1 = g.add_node(2, ()); let v2 = g.add_node(2, ()); let v3 = g.add_node(1, ()); g.add_undirected_edge(v0, v1, 10);
1057 g.add_directed_edge(v1, v2, 20);
1058 g.add_undirected_edge(v2, v3, 30);
1059
1060 let cf1 = g.canonize();
1061
1062 let mut g2 = Graph::<i32, (), i32>::new();
1065 let w1 = g2.add_node(2, ()); let w2 = g2.add_node(2, ()); let w0 = g2.add_node(1, ()); let w3 = g2.add_node(1, ()); g2.add_undirected_edge(w0, w1, 10); g2.add_directed_edge(w1, w2, 20); g2.add_undirected_edge(w2, w3, 30); let cf2 = g2.canonize();
1075
1076 let canon1_nodes: Vec<i32> = (0..cf1.graph.node_count())
1079 .map(|i| *cf1.graph.node_data(i))
1080 .collect();
1081 let canon2_nodes: Vec<i32> = (0..cf2.graph.node_count())
1082 .map(|i| *cf2.graph.node_data(i))
1083 .collect();
1084 assert_eq!(canon1_nodes, canon2_nodes, "canonical node colours differ");
1085
1086 assert_eq!(
1088 cf1.automorphism_group_size, cf2.automorphism_group_size,
1089 "automorphism group sizes differ"
1090 );
1091 }
1092
1093 #[test]
1094 fn stress_64_vertex_random_graph() {
1095 let mut g = Graph::<i32, (), ()>::new();
1098 for i in 0..64 {
1099 g.add_node(i % 4, ()); }
1101 for i in 0..64 {
1103 let a = i;
1104 let b = (i + 1) % 64;
1105 let c = (i + 3) % 64;
1106 let d = (i + 7) % 64;
1107 g.add_undirected_edge(a, b, ());
1108 if i % 2 == 0 {
1109 g.add_directed_edge(a, c, ());
1110 } else {
1111 g.add_undirected_edge(a, c, ());
1112 }
1113 g.add_undirected_edge(a, d, ());
1114 }
1115 let cf = g.canonize();
1116 let mut sorted: Vec<usize> = cf.vertex_map.clone();
1118 sorted.sort();
1119 assert_eq!(sorted, (0..64).collect::<Vec<_>>());
1120 assert_eq!(cf.graph.node_count(), 64);
1122 }
1123}