use std::collections::HashMap;
use crate::cyclotomic::linalg::wedge_sign;
use crate::cyclotomic::{IsRing, Units};
pub type StateId = u32;
pub type Transition = Option<(StateId, (i64, i64))>;
pub(crate) fn cell_basis<ZZ: IsRing>() -> (ZZ, ZZ) {
let k = (ZZ::turn() + 2) / 4; (ZZ::one(), <ZZ as Units>::unit(k))
}
pub(crate) fn fold<ZZ: IsRing>(p: &ZZ) -> ((i64, i64), ZZ) {
let (u, v) = cell_basis::<ZZ>();
let two = ZZ::one() + ZZ::one();
let duv = wedge_sign(&u, &v); let mut q = *p;
let mut k = 0i64;
loop {
if wedge_sign(&(two * q - u), &v) * duv >= 0 {
q = q - u;
k += 1;
} else if wedge_sign(&(two * q + u), &v) * duv < 0 {
q = q + u;
k -= 1;
} else {
break;
}
}
let mut m = 0i64;
loop {
if wedge_sign(&u, &(two * q - v)) * duv >= 0 {
q = q - v;
m += 1;
} else if wedge_sign(&u, &(two * q + v)) * duv < 0 {
q = q + v;
m -= 1;
} else {
break;
}
}
((k, m), q)
}
pub(crate) fn normalize<ZZ: IsRing>(p: &ZZ) -> ZZ {
fold(p).1
}
pub(crate) fn cell_of<ZZ: IsRing>(p: &ZZ) -> (i64, i64) {
fold(p).0
}
pub(crate) fn cell_anchor<ZZ: IsRing>((x, y): (i64, i64)) -> ZZ {
let (u, v) = cell_basis::<ZZ>();
ZZ::from(x) * u + ZZ::from(y) * v
}
#[derive(Clone, Debug)]
pub struct StateAlphabet<ZZ> {
reps: Vec<ZZ>,
index: HashMap<ZZ, StateId>,
trans: Vec<Vec<Transition>>,
dist: Vec<u32>,
radius: u32,
}
impl<ZZ: IsRing> StateAlphabet<ZZ> {
pub fn build(radius: u32) -> Self {
let turn = ZZ::turn() as usize;
let origin = normalize(&ZZ::zero());
let mut reps: Vec<ZZ> = vec![origin];
let mut index: HashMap<ZZ, StateId> = HashMap::new();
index.insert(origin, 0);
let mut dist: Vec<u32> = vec![0];
let mut frontier: Vec<StateId> = vec![0];
for d in 1..=radius {
let mut next_frontier: Vec<StateId> = Vec::new();
for &s in &frontier {
let rep = reps[s as usize];
for k in 0..turn {
let nrep = normalize(&(rep + <ZZ as Units>::unit(k as i8)));
if let std::collections::hash_map::Entry::Vacant(e) = index.entry(nrep) {
let id = reps.len() as StateId;
e.insert(id);
reps.push(nrep);
dist.push(d);
next_frontier.push(id);
}
}
}
frontier = next_frontier;
}
let mut trans: Vec<Vec<Transition>> = vec![vec![None; turn]; reps.len()];
for (s, row) in trans.iter_mut().enumerate() {
let rep = reps[s];
for (k, cell) in row.iter_mut().enumerate() {
let stepped = rep + <ZZ as Units>::unit(k as i8);
let (delta, nrep) = fold(&stepped);
if let Some(&nid) = index.get(&nrep) {
*cell = Some((nid, delta));
}
}
}
Self {
reps,
index,
trans,
dist,
radius,
}
}
pub fn len(&self) -> usize {
self.reps.len()
}
pub fn is_empty(&self) -> bool {
self.reps.is_empty()
}
pub fn radius(&self) -> u32 {
self.radius
}
pub fn turn(&self) -> usize {
self.trans.first().map_or(0, Vec::len)
}
pub fn origin(&self) -> StateId {
0
}
pub fn rep(&self, s: StateId) -> ZZ {
self.reps[s as usize]
}
pub fn dist(&self, s: StateId) -> u32 {
self.dist[s as usize]
}
pub fn state_of(&self, normalized: &ZZ) -> Option<StateId> {
self.index.get(normalized).copied()
}
pub fn step(&self, s: StateId, d: usize) -> Transition {
self.trans[s as usize][d]
}
pub fn states(&self) -> impl Iterator<Item = StateId> {
0..self.reps.len() as StateId
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cyclotomic::{One, OneImag, ReImSign, ZZ12};
fn assert_in_base_cell(q: &ZZ12) {
let two = ZZ12::one() + ZZ12::one();
let i = <ZZ12 as OneImag>::one_i();
assert!((two * *q - ZZ12::one()).re_sign() < 0, "re < 1/2");
assert!((two * *q + ZZ12::one()).re_sign() >= 0, "re >= -1/2");
assert!((two * *q - i).im_sign() < 0, "im < 1/2");
assert!((two * *q + i).im_sign() >= 0, "im >= -1/2");
}
#[test]
fn zz12_shell_counts() {
let alph = StateAlphabet::<ZZ12>::build(8);
let mut per_shell = [0usize; 9];
for s in alph.states() {
per_shell[alph.dist(s) as usize] += 1;
}
assert_eq!(per_shell[0], 1, "origin");
for (i, &count) in per_shell.iter().enumerate().skip(1) {
assert_eq!(count, 4 * i, "shell {i}");
}
assert_eq!(alph.len(), 145);
}
#[test]
fn cardinals_fold_to_origin() {
let alph = StateAlphabet::<ZZ12>::build(4);
let o = alph.origin();
let expect = [(0usize, (1, 0)), (3, (0, 1)), (6, (-1, 0)), (9, (0, -1))];
for (d, delta) in expect {
assert_eq!(alph.step(o, d), Some((o, delta)), "cardinal dir {d}");
}
}
#[test]
fn states_lie_in_base_cell() {
let alph = StateAlphabet::<ZZ12>::build(6);
for s in alph.states() {
assert_in_base_cell(&alph.rep(s));
}
}
#[test]
fn transitions_round_trip() {
let alph = StateAlphabet::<ZZ12>::build(6);
let turn = alph.turn();
let hturn = turn / 2;
for s in alph.states() {
for d in 0..turn {
if let Some((s2, (dx, dy))) = alph.step(s, d) {
let back = alph.step(s2, (d + hturn) % turn);
assert_eq!(
back,
Some((s, (-dx, -dy))),
"round trip s={s} d={d} -> s2={s2}"
);
}
}
}
}
#[test]
fn state_of_round_trips_reps() {
let alph = StateAlphabet::<ZZ12>::build(5);
for s in alph.states() {
assert_eq!(alph.state_of(&alph.rep(s)), Some(s));
}
}
}