#![warn(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
unused_labels,
unused_results
)]
mod refine;
use crate::refine::Partition;
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::collections::BTreeMap;
use std::rc::Rc;
pub trait Canonize
where
Self: Sized + Ord + Clone,
{
fn size(&self) -> usize;
fn apply_morphism(&self, perm: &[usize]) -> Self;
fn invariant_coloring(&self) -> Option<Vec<u64>> {
None
}
fn invariant_neighborhood(&self, _u: usize) -> Vec<Vec<usize>> {
Vec::new()
}
fn canonical(&self) -> Self {
self.canonical_typed(0)
}
fn canonical_typed(&self, sigma: usize) -> Self {
let partition = Partition::with_singletons(self.size(), sigma);
canonical_constraint(self, partition)
}
#[inline]
fn morphism_to_canonical(&self) -> Vec<usize> {
self.morphism_to_canonical_typed(0)
}
fn morphism_to_canonical_typed(&self, sigma: usize) -> Vec<usize> {
assert!(sigma <= self.size());
let partition = Partition::with_singletons(self.size(), sigma);
morphism_to_canonical_constraint(self, partition)
}
#[inline]
fn automorphisms(&self) -> AutomorphismIterator<Self> {
self.stabilizer(0)
}
#[inline]
fn stabilizer(&self, sigma: usize) -> AutomorphismIterator<Self> {
let mut partition = Partition::simple(self.size());
for i in 0..sigma {
let _ = partition.individualize(i);
}
AutomorphismIterator::new(self, partition)
}
}
fn target_selector(part: &Partition) -> Option<usize> {
let mut min = usize::max_value();
let mut arg_min = None;
for i in part.parts() {
let length = part.part(i).len();
if 2 <= length && (length < min) {
min = length;
arg_min = Some(i);
}
}
arg_min
}
fn precompute_invariant<F>(g: &F) -> Vec<Vec<Vec<usize>>>
where
F: Canonize,
{
let n = g.size();
let mut res = Vec::with_capacity(n);
for i in 0..n {
res.push(g.invariant_neighborhood(i))
}
res
}
fn refine(partition: &mut Partition, invariants: &[Vec<Vec<usize>>], new_part: Option<usize>) {
if !partition.is_discrete() {
let n = partition.num_elems();
assert!(n >= 2);
let invariant_size = invariants[0].len();
debug_assert!(invariants.iter().all(|v| v.len() == invariant_size));
let mut stack: Vec<_> = match new_part {
Some(p) => vec![p],
None => partition.parts().collect(),
};
let max_step = ((n + 1 - partition.num_parts()) as u64).pow(invariant_size as u32);
let threshold = u64::max_value() / max_step;
let mut part_buffer = Vec::new();
while !stack.is_empty() && !partition.is_discrete() {
let mut weight = 1;
while let Some(part) = stack.pop() {
part_buffer.clear();
part_buffer.extend_from_slice(partition.part(part));
let factor = (part_buffer.len() + 1) as u64;
for i in 0..invariant_size {
weight *= factor;
for &u in &part_buffer {
for &v in &invariants[u][i] {
partition.sieve(v, weight)
}
}
}
if weight > threshold {
break;
};
}
partition.split(|new| {
stack.push(new);
})
}
}
}
fn fca(u: &[usize], v: &[usize]) -> usize {
let mut i = 0;
while i < u.len() && i < v.len() && u[i] == v[i] {
i += 1;
}
i
}
#[derive(Clone, Debug)]
struct IsoTreeNode {
nparts: usize,
children: Vec<usize>,
inv: Rc<Vec<Vec<Vec<usize>>>>,
}
impl IsoTreeNode {
fn root<F: Canonize>(partition: &mut Partition, g: &F) -> Self {
let inv = Rc::new(precompute_invariant(g));
if let Some(coloring) = g.invariant_coloring() {
partition.refine_by_value(&coloring, |_| {})
}
Self::new(partition, inv, None)
}
fn new(
partition: &mut Partition,
inv: Rc<Vec<Vec<Vec<usize>>>>,
new_part: Option<usize>,
) -> Self {
refine(partition, &inv, new_part);
Self {
children: match target_selector(partition) {
Some(set) => partition.part(set).to_vec(),
None => Vec::new(),
},
nparts: partition.num_parts(),
inv,
}
}
fn explore(&self, v: usize, pi: &mut Partition) -> Self {
debug_assert!(self.is_restored(pi));
let new_part = pi.individualize(v);
Self::new(pi, self.inv.clone(), new_part)
}
fn dummy() -> Self {
Self {
children: Vec::new(),
nparts: 1,
inv: Rc::new(Vec::new()),
}
}
fn restore(&self, partition: &mut Partition) {
partition.undo(self.nparts)
}
fn is_restored(&self, partition: &mut Partition) -> bool {
partition.num_parts() == self.nparts
}
}
fn canonical_constraint<F>(g: &F, mut partition: Partition) -> F
where
F: Canonize,
{
let mut zeta: BTreeMap<F, Vec<usize>> = BTreeMap::new();
let mut tree = Vec::new();
let mut path = Vec::new();
let mut node = IsoTreeNode::root(&mut partition, g);
loop {
if let Some(phi) = partition.as_bijection() {
match zeta.entry(g.apply_morphism(phi)) {
Occupied(entry) =>
{
let k = fca(entry.get(), &path) + 1;
tree.truncate(k);
path.truncate(k);
}
Vacant(entry) => {
let _ = entry.insert(path.clone());
}
}
};
if let Some(u) = node.children.pop() {
let new_node = node.explore(u, &mut partition);
tree.push(node);
path.push(u);
node = new_node;
} else {
match tree.pop() {
Some(n) => {
node = n;
let _ = path.pop();
node.restore(&mut partition);
}
None => break,
}
};
}
let (g_max, _) = zeta.into_iter().next_back().unwrap();
g_max
}
#[derive(Clone, Debug)]
pub struct AutomorphismIterator<F> {
tree: Vec<IsoTreeNode>,
node: IsoTreeNode,
partition: Partition,
g: F,
}
impl<F: Canonize> AutomorphismIterator<F> {
fn new(g: &F, mut partition: Partition) -> Self {
debug_assert!(g == &canonical_constraint(g, partition.clone()));
Self {
tree: vec![IsoTreeNode::root(&mut partition, g)],
partition,
node: IsoTreeNode::dummy(),
g: g.clone(),
}
}
}
impl<F: Canonize> Iterator for AutomorphismIterator<F> {
type Item = Vec<usize>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(u) = self.node.children.pop() {
let new_node = self.node.explore(u, &mut self.partition);
let old_node = std::mem::replace(&mut self.node, new_node);
self.tree.push(old_node)
} else {
match self.tree.pop() {
Some(n) => {
n.restore(&mut self.partition);
self.node = n
}
None => return None,
}
}
if let Some(phi) = self.partition.as_bijection() {
if self.g.apply_morphism(phi) == self.g {
return Some(phi.to_vec());
}
};
}
}
}
fn morphism_to_canonical_constraint<F>(g: &F, mut partition: Partition) -> Vec<usize>
where
F: Canonize,
{
let mut tree = Vec::new();
let mut node = IsoTreeNode::root(&mut partition, g);
let mut max = None;
let mut phimax = Vec::new();
loop {
if let Some(phi) = partition.as_bijection() {
let phi_g = Some(g.apply_morphism(phi));
if phi_g > max {
max = phi_g;
phimax = phi.to_vec();
}
};
if let Some(u) = node.children.pop() {
let new_node = node.explore(u, &mut partition);
tree.push(node);
node = new_node;
} else {
match tree.pop() {
Some(n) => {
n.restore(&mut partition);
node = n
}
None => break,
}
}
}
phimax
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Ord, PartialOrd, PartialEq, Eq, Clone, Debug)]
struct Graph {
adj: Vec<Vec<usize>>,
}
impl Graph {
fn new(n: usize, edges: &[(usize, usize)]) -> Self {
let mut adj = vec![Vec::new(); n];
for &(u, v) in edges {
adj[u].push(v);
adj[v].push(u);
}
Graph { adj }
}
}
impl Canonize for Graph {
fn size(&self) -> usize {
self.adj.len()
}
fn apply_morphism(&self, perm: &[usize]) -> Self {
let mut adj = vec![Vec::new(); self.size()];
for (i, nbrs) in self.adj.iter().enumerate() {
adj[perm[i]] = nbrs.iter().map(|&u| perm[u]).collect();
adj[perm[i]].sort();
}
Graph { adj }
}
fn invariant_neighborhood(&self, u: usize) -> Vec<Vec<usize>> {
vec![self.adj[u].clone()]
}
}
#[test]
fn graph() {
let c5 = Graph::new(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]);
let other_c5 = Graph::new(5, &[(0, 2), (2, 1), (1, 4), (4, 3), (3, 0)]);
assert_eq!(c5.canonical(), other_c5.canonical());
let p5 = Graph::new(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
assert!(c5.canonical() != p5.canonical());
let p = c5.morphism_to_canonical();
assert_eq!(c5.apply_morphism(&p), c5.canonical());
}
#[test]
fn automorphisms_iterator() {
let c4 = Graph::new(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]).canonical();
let mut count = 0;
for phi in c4.automorphisms() {
assert_eq!(c4.apply_morphism(&phi), c4);
count += 1;
}
assert_eq!(count, 8)
}
}