use crate::classify::lattice::lattice_from_orbit;
use crate::classify::tiling::{Tiling, verify_tiling};
use crate::cyclotomic::IsRing;
use crate::geom::iso::{Iso, build_orbit, cryst_order, gluing_iso};
use crate::geom::patch::boundary_vertices;
fn for_each_involution(n: usize, f: &mut impl FnMut(&[usize]) -> bool) {
fn go(i: usize, n: usize, cur: &mut Vec<usize>, f: &mut impl FnMut(&[usize]) -> bool) -> bool {
if i == n {
return f(cur);
}
if cur[i] != usize::MAX {
return go(i + 1, n, cur, f);
}
cur[i] = i; let cont = go(i + 1, n, cur, f);
cur[i] = usize::MAX;
if !cont {
return false;
}
for j in (i + 1)..n {
if cur[j] == usize::MAX {
cur[i] = j;
cur[j] = i;
let cont = go(i + 1, n, cur, f);
cur[i] = usize::MAX;
cur[j] = usize::MAX;
if !cont {
return false;
}
}
}
true
}
let mut cur = vec![usize::MAX; n];
go(0, n, &mut cur, f);
}
pub fn isohedral_tiling<T: IsRing>(
seq: &[i8],
radius: f64,
cap: usize,
max_builds: usize,
) -> Option<Tiling<T>> {
let verts = boundary_vertices::<T>(seq);
let n = verts.len();
let mut g = vec![vec![None::<Iso<T>>; n]; n];
let mut ok = vec![vec![false; n]; n];
for e in 0..n {
for f in 0..n {
if let Some(iso) = gluing_iso(&verts, e, f)
&& cryst_order::<T>(iso.rot).is_some()
{
g[e][f] = Some(iso);
ok[e][f] = true;
}
}
}
let mut builds = 0usize;
let mut found: Option<Tiling<T>> = None;
for_each_involution(n, &mut |sigma| {
if !(0..n).all(|e| ok[e][sigma[e]]) {
return true; }
if builds >= max_builds {
return false; }
builds += 1;
let mut gens: Vec<Iso<T>> = (0..n).map(|e| g[e][sigma[e]].unwrap()).collect();
let invs: Vec<Iso<T>> = gens.iter().map(|x| x.inv()).collect();
gens.extend(invs); let placements = build_orbit(&verts, &gens, radius, cap);
if placements.len() >= cap {
return true; }
let lattice = lattice_from_orbit(&placements).unwrap_or((T::zero(), T::zero()));
let tiling = Tiling {
verts: verts.clone(),
seq: seq.to_vec(),
lattice,
placements,
};
if verify_tiling(&tiling).ok() {
found = Some(tiling);
return false; }
true
});
found
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cyclotomic::ZZ12;
use crate::geom::rat::Rat;
use crate::geom::tiles;
#[test]
fn isohedral_search_finds_known_tilers() {
let square = Rat::from_snake_trusted(&tiles::square::<ZZ12>());
let triangle = Rat::from_snake_trusted(&tiles::triangle::<ZZ12>());
let hexagon = Rat::from_snake_trusted(&tiles::hexagon::<ZZ12>());
for (name, rat) in [
("square", &square),
("triangle", &triangle),
("hexagon", &hexagon),
] {
let p = rat.seq().len() as f64;
let t = isohedral_tiling::<ZZ12>(rat.seq(), 2.6 * p, 1200, 500);
assert!(t.is_some(), "{name} must yield a verified isohedral tiling");
eprintln!("{name}: {} tiles", t.unwrap().placements.len());
}
}
}