use crate::classify::cascade::PeriodicVia;
use crate::classify::cert::{HeeschCert, HeeschStatus, PeriodicCert, base_verts, meta_of};
use crate::classify::grow::grow_coronas_build;
use crate::classify::heesch::{Heesch, count_coronas, heesch_number_witnessed};
use crate::classify::lattice::{basis_inverse, in_lattice, lay_lattice_block};
use crate::classify::tiling::{AREA_EPS, Tiling};
use crate::classify::torus::{TorusCover, torus_cover_from_patch};
use crate::cyclotomic::IsRing;
use crate::cyclotomic::geometry::float::area_f;
use crate::geom::iso::{Iso, gluing_iso};
use crate::geom::matches::{EdgeRange, PatchMatch, Segment, TileMatch};
use crate::geom::patch::BasicPatch;
use crate::geom::patch::graph::{WithAdjacency, assemble};
use crate::geom::rat::Rat;
use crate::geom::tileset::TileSet;
use crate::stringmatch::lex_min_rot;
use std::collections::VecDeque;
pub(crate) fn torus_cert<T: IsRing>(
base: &Rat<T>,
kmax: usize,
coronas: usize,
) -> Option<PeriodicCert> {
let (pls, ig) = grow_coronas_build::<T>(base.seq(), coronas)?;
mint_cover(base, &ig, &pls, kmax)
}
fn mint_cover<T: IsRing>(
base: &Rat<T>,
ig: &WithAdjacency<BasicPatch<T>>,
pls: &[Iso<T>],
kmax: usize,
) -> Option<PeriodicCert> {
let verts = base_verts(base);
let tile_area = area_f(&verts);
if tile_area < AREA_EPS {
return None;
}
let cover = torus_cover_from_patch(&verts, base.seq(), pls, tile_area, kmax)?;
if let Some(pc) = cert_from_placements(
base,
&lay_cover(cover.lattice, &cover.domain),
PeriodicVia::Torus(1),
) {
return Some(pc);
}
carve_domain(base, ig, pls, &cover)
}
fn carve_domain<T: IsRing>(
base: &Rat<T>,
ig: &WithAdjacency<BasicPatch<T>>,
pls: &[Iso<T>],
cover: &TorusCover<T>,
) -> Option<PeriodicCert> {
let trace = crate::classify::trace::cover();
let verts = base_verts(base);
let k = cover.k;
let (w1, w2) = cover.lattice;
let winv = basis_inverse(&w1, &w2)?;
let coset = |iso: &Iso<T>| -> usize {
cover
.domain
.iter()
.position(|r| r.rot == iso.rot && in_lattice(iso.shift - r.shift, w1, w2, &winv))
.unwrap_or(k)
};
if pls.len() != ig.num_tiles() {
return None;
}
let cls: Vec<usize> = pls.iter().map(coset).collect();
if cls[0] >= k {
return None; }
let dom = connected_transversal(ig.adj(), &cls, k, 0)?;
if trace {
eprintln!("CARVE: {} tiles, k={k}, dom={dom:?}", pls.len());
}
let (build, gpm, order) = assemble(ig.shape(), ig.adj(), &dom, base)?;
let m = verts.len();
let bn = gpm.len();
let mut ep: Vec<(T, T)> = Vec::with_capacity(bn);
for i in 0..bn {
let t = order[gpm.patch_tile_ids()[i]]; let off = gpm.edges()[i].canon_offset;
let poly = pls[t].tile(&verts);
ep.push((poly[off], poly[(off + 1) % m]));
}
let mut glue: Vec<TileMatch> = Vec::with_capacity(bn);
for i in 0..bn {
let (a, b) = ep[i];
let partner = (0..bn).find(|&j| {
if j == i {
return false;
}
let (c, d) = ep[j];
a - d == b - c && in_lattice(a - d, w1, w2, &winv)
});
let Some(j) = partner else {
if trace {
eprintln!("CARVE: meta boundary edge {i} has no lattice-translate partner");
}
return None;
};
glue.push(TileMatch::new(
Segment::new(0, EdgeRange::new(i, 1)),
Segment::new(0, EdgeRange::new(j, 1)),
));
}
let via = PeriodicVia::Torus(build.len() + 1);
let cert = PeriodicCert { build, glue, via };
cert.verify(base).then_some(cert)
}
pub(crate) fn connected_transversal(
adj: &[Vec<TileMatch>],
class: &[usize],
k: usize,
root: usize,
) -> Option<Vec<usize>> {
debug_assert_eq!(class.len(), adj.len());
if class[root] >= k {
return None;
}
let mut chosen: Vec<Option<usize>> = vec![None; k]; chosen[class[root]] = Some(root);
let mut queue = VecDeque::from([class[root]]);
let mut placed = 1;
while let Some(ca) = queue.pop_front() {
let na = chosen[ca].expect("class in queue is placed");
for e in &adj[na] {
let cb = class[e.b.tile_id];
if cb < k && chosen[cb].is_none() {
chosen[cb] = Some(e.b.tile_id);
placed += 1;
queue.push_back(cb);
}
}
}
if placed != k {
return None;
}
let mut out = Vec::with_capacity(k);
out.push(root);
for (c, node) in chosen.iter().enumerate() {
if c != class[root] {
out.push(node.expect("all classes placed"));
}
}
Some(out)
}
fn lay_cover<T: IsRing>(lattice: (T, T), domain: &[Iso<T>]) -> Vec<Iso<T>> {
let (v1, v2) = lattice;
const LAY_REACH: i64 = 4;
lay_lattice_block(domain, v1, v2, LAY_REACH, LAY_REACH)
}
pub(crate) fn witness_torus_cert<T: IsRing>(
base: &Rat<T>,
witness: &[PatchMatch],
kmax: usize,
) -> Option<PeriodicCert> {
if witness.is_empty() {
return None;
}
let (pls, ig) = crate::classify::grow::replay_placements_graph(base, witness)?;
mint_cover(base, &ig, &pls, kmax)
}
pub(crate) fn translation_cert<T: IsRing>(base: &Rat<T>, v1: T, v2: T) -> Option<PeriodicCert> {
cert_from_placements(
base,
&lay_cover((v1, v2), &[Iso::id()]),
PeriodicVia::Translation,
)
}
fn cert_from_placements<T: IsRing>(
base: &Rat<T>,
placements: &[Iso<T>],
via: PeriodicVia,
) -> Option<PeriodicCert> {
let glue = glue_from_corona(&base_verts(base), placements)?;
let cert = PeriodicCert {
build: vec![],
glue,
via,
};
cert.verify(base).then_some(cert)
}
pub fn cert_from_cluster<T: IsRing>(
base: &Rat<T>,
build: &[PatchMatch],
cluster_tiling: &Tiling<T>,
via: PeriodicVia,
) -> Option<PeriodicCert> {
let trace = crate::classify::trace::cover();
let Some((_meta_verts, meta_seq, _k)) = meta_of(build, base) else {
if trace {
eprintln!("CLUSTER: meta_of None");
}
return None;
};
let Some(glue_canon) = glue_from_corona(&cluster_tiling.verts, &cluster_tiling.placements)
else {
if trace {
eprintln!("CLUSTER: glue_from_corona None");
}
return None;
};
let (n, r) = (meta_seq.len(), lex_min_rot(&meta_seq));
let glue = glue_canon
.iter()
.map(|tm| {
TileMatch::new(
Segment::new(0, EdgeRange::new((tm.a.range.start_offset + r) % n, 1)),
Segment::new(0, EdgeRange::new((tm.b.range.start_offset + r) % n, 1)),
)
})
.collect();
let cert = PeriodicCert {
build: build.to_vec(),
glue,
via,
};
let ok = cert.verify(base);
if trace && !ok {
eprintln!("CLUSTER: verify failed");
}
ok.then_some(cert)
}
pub fn cert_from_tiling<T: IsRing>(
base: &Rat<T>,
tiling: &Tiling<T>,
via: PeriodicVia,
) -> Option<PeriodicCert> {
cert_from_placements(base, &tiling.placements, via)
}
fn glue_from_corona<T: IsRing>(verts: &[T], placements: &[Iso<T>]) -> Option<Vec<TileMatch>> {
let n = verts.len();
let mut glue = Vec::with_capacity(n);
for e in 0..n {
let (a0, a1) = (verts[e], verts[(e + 1) % n]);
let neighbour = placements.iter().find(|p| {
if p.rot == 0 && p.shift.xy() == (0.0, 0.0) {
return false; }
let tile = p.tile(verts);
(0..n).any(|f| tile[f] == a1 && tile[(f + 1) % n] == a0)
})?;
let f = (0..n).find(|&f| gluing_iso(verts, e, f).as_ref() == Some(neighbour))?;
glue.push(TileMatch::new(
Segment::new(0, EdgeRange::new(e, 1)),
Segment::new(0, EdgeRange::new(f, 1)),
));
}
Some(glue)
}
pub fn heesch_cert<T: IsRing>(
base: &Rat<T>,
h: Heesch,
build: Vec<PatchMatch>,
bound: usize,
budget: usize,
) -> HeeschCert {
let (heesch, status) = match h {
Heesch::Finite(k) => (k, HeeschStatus::Finite),
Heesch::Unknown(k) => (k, HeeschStatus::Unknown),
Heesch::AtLeast(b) => (b, HeeschStatus::Unknown),
};
debug_assert!(
heesch <= u8::MAX as usize && bound <= u8::MAX as usize,
"heesch/bound fit u8"
);
debug_assert!(
count_coronas(base, &build) >= heesch,
"build must witness >= heesch coronas"
);
HeeschCert {
heesch: heesch as u8,
status,
build,
bound: bound as u8,
budget: budget.min(u32::MAX as usize) as u32,
}
}
pub fn heesch_cert_for<T: IsRing>(base: &Rat<T>, bound: usize, budget: usize) -> HeeschCert {
let ts = TileSet::single(base.clone());
let (h, build) = heesch_number_witnessed(ts, 0, bound, budget);
heesch_cert(base, h, build, bound, budget)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::classify::cascade::AcceptBounds;
use crate::classify::conway::build_tiling;
use crate::classify::isohedral::isohedral_tiling;
use crate::classify::tiling::ORBIT_RADIUS_FACTOR;
use crate::geom::tiles;
#[test]
fn heesch_cert_records_and_verifies_lower_bound() {
use crate::cyclotomic::ZZ12;
let dodec = Rat::<ZZ12>::from_snake_trusted(&tiles::dodecagon());
let c0 = heesch_cert_for(&dodec, 2, 200_000);
assert_eq!((c0.heesch, c0.status), (0, HeeschStatus::Finite));
assert!(c0.build.is_empty());
assert!(c0.verify_lower_bound(&dodec));
let t1 = Rat::<ZZ12>::from_slice_trusted(&[-2, 1, 4, -1, 4, -1, 2, -1, 5, 1]);
let c1 = heesch_cert_for(&t1, 2, 20_000_000);
assert_eq!((c1.heesch, c1.status), (1, HeeschStatus::Finite));
assert!(
!c1.build.is_empty(),
"Heesch-1 reject keeps a 1-corona witness"
);
assert!(c1.verify_lower_bound(&t1));
let mut overclaim = c1.clone();
overclaim.heesch = 2;
assert!(
!overclaim.verify_lower_bound(&t1),
"witness only proves >= 1"
);
}
#[test]
fn periodic_cert_mints_across_detectors() {
use crate::cyclotomic::ZZ12;
let bounds = AcceptBounds::default();
let tri = Rat::<ZZ12>::from_snake_trusted(&tiles::triangle());
let hex = Rat::<ZZ12>::from_snake_trusted(&tiles::hexagon());
let specimens: [(&str, &[i8], bool); 5] = [
("triangle", tri.seq(), true),
("hexagon", hex.seq(), true),
("iso", &[-4, 3, 4, 1, 3, 5], true),
("tile0", &[-4, 3, 4, 0, 4, -3, 5, 3], false),
("unknown", &[-2, -1, 2, 5, -2, 1, 2, 1, 2, 4], false),
];
for (name, seq, must) in specimens {
let base = Rat::<ZZ12>::from_slice_trusted(seq);
let radius = ORBIT_RADIUS_FACTOR * seq.len() as f64;
let cert = build_tiling::<ZZ12>(seq, radius, 2_000)
.or_else(|| isohedral_tiling::<ZZ12>(seq, radius, 2_000, bounds.iso_builds))
.and_then(|t| cert_from_tiling(&base, &t, PeriodicVia::Conway))
.or_else(|| {
torus_cert::<ZZ12>(
&Rat::from_slice_trusted(seq),
bounds.torus_kmax,
bounds.torus_coronas,
)
});
let Some(cert) = cert else {
assert!(!must, "{name}: should mint a periodic cert");
eprintln!(
"{name}: no cert from this minor path (high-k tail); certify_periodic would catch it"
);
continue;
};
assert!(cert.verify(&base), "{name}: minted cert must self-verify");
let pls = cert.grow(&base, 6.0, 400).expect("grow");
let distinct: std::collections::HashSet<_> = pls.iter().copied().collect();
assert_eq!(
distinct.len(),
pls.len(),
"{name}: grow has no duplicate placements"
);
eprintln!(
"{name}: build={} glue={} grew={}",
cert.build.len(),
cert.glue.len(),
pls.len()
);
}
}
#[test]
fn torus_cert_mints_verifies_grows() {
use crate::cyclotomic::ZZ12;
let bounds = AcceptBounds::default();
let tri = Rat::<ZZ12>::from_snake_trusted(&tiles::triangle());
let hex = Rat::<ZZ12>::from_snake_trusted(&tiles::hexagon());
let candidates: [&[i8]; 2] = [
tri.seq(), hex.seq(), ];
for seq in candidates {
let cert = torus_cert::<ZZ12>(
&Rat::from_slice_trusted(seq),
bounds.torus_kmax,
bounds.torus_coronas,
)
.unwrap_or_else(|| panic!("{seq:?}: should mint a periodic cert"));
let base = Rat::<ZZ12>::from_slice_trusted(seq);
assert!(cert.verify(&base), "{seq:?}: minted cert must self-verify");
let placements = cert.grow(&base, 6.0, 400).expect("grow succeeds");
assert!(placements.len() >= 7, "{seq:?}: grow yields a real patch");
let distinct: std::collections::HashSet<_> = placements.iter().copied().collect();
assert_eq!(
distinct.len(),
placements.len(),
"{seq:?}: no duplicate placements"
);
let mut bad = cert.clone();
bad.glue.clear();
assert!(
!bad.verify(&base),
"{seq:?}: glue with no generators must fail verify"
);
eprintln!(
"minted {seq:?}: build={} tiles, glue={} pairs, grew {} copies",
cert.build.len(),
cert.glue.len(),
placements.len()
);
}
}
#[test]
#[ignore = "needs web/ratdb asset + WITNESS_STASH (pre-merge n=14 Undecided lines)"]
fn witness_torus_certifies_k18_pair_from_stash() {
use crate::classify::cert::Classified;
use crate::classify::classify_tiles::open_ratdb;
use crate::cyclotomic::ZZ12;
let stash = std::env::var("WITNESS_STASH").expect("WITNESS_STASH=path");
let d = open_ratdb("web/ratdb/data/zz12_n16_free");
let txt = std::fs::read_to_string(stash).unwrap();
let mut hits = 0;
for line in txt.lines() {
let (idx_s, json) = line.split_once('\t').unwrap();
let idx: u64 = idx_s.parse().unwrap();
if idx != 16856076 && idx != 23665356 {
continue;
}
let Ok(Classified::Undecided { corona, .. }) = serde_json::from_str(json) else {
panic!("stash line {idx} not Undecided")
};
let base = Rat::<ZZ12>::from_slice_trusted(&d.get(idx).unwrap());
let t = std::time::Instant::now();
let pc = witness_torus_cert(&base, &corona, 40)
.expect("stored witness exposes the k=18 lattice");
assert!(pc.verify(&base), "idx {idx}: witness-minted cert verifies");
eprintln!(
"idx {idx}: via {:?}, meta {} copies, {:.2}s from stored witness",
pc.via,
pc.build.len() + 1,
t.elapsed().as_secs_f64()
);
hits += 1;
}
assert_eq!(hits, 2, "both k=18 tiles certify from their witnesses");
}
#[test]
fn carve_hard_torus_tiles() {
use crate::cyclotomic::ZZ12;
let bounds = AcceptBounds {
torus_kmax: 40,
torus_coronas: 4,
..AcceptBounds::default()
};
let cases: [(&str, &[i8]); 3] = [
("14280 (k=3)", &[-2, -1, 2, 5, -2, 1, 2, 1, 2, 4]),
("11845 (k=4)", &[-3, 1, 3, -2, 4, 3, -2, 1, 2, 5]),
("2621515 (k=14)", &[-4, 0, 2, 0, 4, 0, 0, 2, 0, 2, 0, 4, 2]),
];
for (name, seq) in cases {
let base = Rat::<ZZ12>::from_slice_trusted(seq);
let cert = torus_cert::<ZZ12>(
&Rat::from_slice_trusted(seq),
bounds.torus_kmax,
bounds.torus_coronas,
)
.unwrap_or_else(|| panic!("{name}: torus_cert must mint a cert"));
assert!(cert.verify(&base), "{name}: minted cert must self-verify");
assert!(
!cert.build.is_empty(),
"{name}: k>1 domain has a non-empty build"
);
eprintln!(
"{name}: build={} tiles, glue={} pairs",
cert.build.len(),
cert.glue.len()
);
}
}
}