use std::sync::Arc;
use std::time::Instant;
use serde::{Deserialize, Serialize};
use crate::combinatorics::junctiontypes::{self, OpenJunctionTypeIndex};
use crate::combinatorics::neighborhood::{self, NeighborhoodIndex};
use crate::combinatorics::seq_explorer::{self, SeqExplorer, check_fixed_point};
use crate::cyclotomic::{IsRing, ZZ10, ZZ12};
use crate::geom::rat::Rat;
use crate::geom::tileset::{self, TileSet, TileSetKind};
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum CollectKind {
Nbhd,
Jtype,
Seq,
}
impl CollectKind {
pub const ALL: [&'static str; 3] = ["nbhd", "jtype", "seq"];
}
impl std::str::FromStr for CollectKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"nbhd" => Ok(CollectKind::Nbhd),
"jtype" => Ok(CollectKind::Jtype),
"seq" => Ok(CollectKind::Seq),
other => Err(format!(
"unknown kind '{other}' (expected one of {:?})",
CollectKind::ALL
)),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct Envelope {
pub kind: CollectKind,
pub payload: serde_json::Value,
}
fn ts_zz12(kind: TileSetKind) -> Arc<TileSet<ZZ12>> {
match kind {
TileSetKind::Hex => tileset::hex::<ZZ12>(),
TileSetKind::Square => tileset::square::<ZZ12>(),
TileSetKind::Mixed => tileset::mixed::<ZZ12>(),
TileSetKind::Tetris => tileset::tetrominoes::<ZZ12>(),
TileSetKind::Spectre => tileset::spectre::<ZZ12>(),
TileSetKind::Penrose => panic!("penrose requires ZZ10"),
}
}
fn ts_zz10(kind: TileSetKind) -> Arc<TileSet<ZZ10>> {
match kind {
TileSetKind::Penrose => tileset::penrose::<ZZ10>(),
_ => panic!("only penrose uses ZZ10"),
}
}
fn rebuild_tileset_zz12(tile_angles: &[Vec<i8>]) -> Arc<TileSet<ZZ12>> {
let rats: Vec<Rat<ZZ12>> = tile_angles
.iter()
.map(|s| Rat::<ZZ12>::from_slice_trusted(s))
.collect();
Arc::new(TileSet::new(rats))
}
fn rebuild_tileset_zz10(tile_angles: &[Vec<i8>]) -> Arc<TileSet<ZZ10>> {
let rats: Vec<Rat<ZZ10>> = tile_angles
.iter()
.map(|s| Rat::<ZZ10>::from_slice_trusted(s))
.collect();
Arc::new(TileSet::new(rats))
}
fn collect_nbhd<T: IsRing>(
ts: Arc<TileSet<T>>,
ring: &str,
label: &str,
) -> neighborhood::Collection {
eprintln!("[{label}] Running neighborhood-type BFS...");
let t0 = Instant::now();
let idx = NeighborhoodIndex::new(Arc::clone(&ts));
let elapsed = t0.elapsed();
let kinds = idx.classify_all();
let dead = kinds
.iter()
.filter(|&&k| k == neighborhood::NtKind::Dead)
.count();
let undead = kinds
.iter()
.filter(|&&k| k == neighborhood::NtKind::Undead)
.count();
let blessed = kinds
.iter()
.filter(|&&k| k == neighborhood::NtKind::Blessed)
.count();
let free = kinds
.iter()
.filter(|&&k| k == neighborhood::NtKind::Free)
.count();
eprintln!(
"[{label}] types={} (dead={} undead={} blessed={} free={}) transitions={} time={:.2?}",
idx.num_types(),
dead,
undead,
blessed,
free,
idx.transitions().len(),
elapsed,
);
idx.to_collection(ring)
}
fn collect_jtype<T: IsRing>(
ts: Arc<TileSet<T>>,
ring: &str,
label: &str,
) -> junctiontypes::Collection {
eprintln!("[{label}] Running junction-type BFS...");
let t0 = Instant::now();
let idx = OpenJunctionTypeIndex::new(Arc::clone(&ts));
let elapsed = t0.elapsed();
let entries = idx.entries();
let n_initial = entries.iter().filter(|e| e.is_initial()).count();
let n_free = entries.iter().filter(|e| e.is_free()).count();
let n_blessed = entries.iter().filter(|e| e.is_blessed()).count();
let n_undead = entries.iter().filter(|e| e.is_undead()).count();
let n_dead = entries.iter().filter(|e| e.is_dead()).count();
let n_closed = idx.transitions().iter().filter(|t| t.is_closed()).count();
let n_open = idx.transitions().len() - n_closed;
eprintln!(
"[{label}] types={} (initial={} free={} blessed={} undead={} dead={}) transitions={} ({} open, {} closed) time={:.2?}",
idx.num_types(),
n_initial,
n_free,
n_blessed,
n_undead,
n_dead,
idx.transitions().len(),
n_open,
n_closed,
elapsed,
);
junctiontypes::Collection::from_index(&idx, ring)
}
fn collect_seq<T: IsRing>(
ts: Arc<TileSet<T>>,
ring: &str,
label: &str,
) -> seq_explorer::Collection {
eprintln!("[{label}] Running subseq fixed-point BFS...");
let t0 = Instant::now();
let explorer = SeqExplorer::new(Arc::clone(&ts));
let elapsed = t0.elapsed();
eprintln!(
"[{label}] subseqs={} rats={} k={} time={:.2?}",
explorer.num_subseqs(),
explorer.num_rats(),
explorer.max_subseq_len(),
elapsed,
);
seq_explorer::Collection::from_explorer(&explorer, ring)
}
fn validate_nbhd<T: IsRing>(
coll: neighborhood::Collection,
ts: Arc<TileSet<T>>,
) -> Result<(), String> {
eprintln!(
" Parsed: ring={}, tiles={}, entries={}, transitions={}, kinds={}",
coll.ring,
coll.tile_angles.len(),
coll.entries.len(),
coll.transitions.len(),
coll.kinds.len(),
);
let t0 = Instant::now();
let idx = NeighborhoodIndex::from_collection(ts, coll)?;
let invalid = idx.validate();
if !invalid.is_empty() {
return Err(format!(
"{} entries failed NeighborhoodIndex::validate (ids: {:?}...)",
invalid.len(),
&invalid[..invalid.len().min(5)],
));
}
eprintln!(
" Rebuilt + validated {} entries in {:.2?}",
idx.num_types(),
t0.elapsed(),
);
Ok(())
}
fn validate_jtype<T: IsRing>(
coll: junctiontypes::Collection,
ts: Arc<TileSet<T>>,
) -> Result<(), String> {
let t0 = Instant::now();
eprintln!(
" Parsed: ring={}, tiles={}, jtypes={}, transitions={}",
coll.ring,
coll.tile_angles.len(),
coll.jtypes.len(),
coll.transitions.len(),
);
eprintln!(" Phase 1: Reconstructing witnesses...");
let t1 = Instant::now();
let witnesses = coll.reconstruct_witnesses(&ts)?;
eprintln!(
" Reconstructed {} witnesses in {:.2?}",
witnesses.len(),
t1.elapsed(),
);
eprintln!(" Phase 2: Verifying junction types...");
let t2 = Instant::now();
let jt_errors = coll.jtype_errors(&witnesses);
for (id, msg) in jt_errors.iter().take(5) {
eprintln!(" ERROR: JTYPE {}: {}", id, msg);
}
if !jt_errors.is_empty() {
return Err(format!("{} junction type mismatches", jt_errors.len()));
}
eprintln!(
" All {} junction types verified in {:.2?}",
coll.jtypes.len(),
t2.elapsed(),
);
eprintln!(" Phase 3: Verifying transitions...");
let t3 = Instant::now();
let tr_errors = coll.transition_errors(&ts, &witnesses);
for ((src, dst), msg) in tr_errors.iter().take(5) {
eprintln!(" ERROR: TRANS {} -> {}: {}", src, dst, msg);
}
if !tr_errors.is_empty() {
return Err(format!("{} transition errors", tr_errors.len()));
}
eprintln!(
" All {} transitions verified in {:.2?}",
coll.transitions.len(),
t3.elapsed(),
);
eprintln!(" Phase 4: Completeness check...");
let t4 = Instant::now();
let report = coll.completeness_errors(&witnesses);
for id in report.missing.iter().take(10) {
eprintln!(" MISSING: JTYPE {} produces unknown junction type", id);
}
if !report.is_complete() {
return Err(format!(
"Completeness FAILED: {} junction types produce unknown junction types",
report.missing.len(),
));
}
eprintln!(
" Completeness: {} match checks passed in {:.2?}",
report.matches_checked,
t4.elapsed(),
);
eprintln!(" Validation PASSED in {:.2?}", t0.elapsed());
Ok(())
}
fn validate_seq<T: IsRing>(
coll: seq_explorer::Collection,
ts: Arc<TileSet<T>>,
) -> Result<(), String> {
let t0 = Instant::now();
let k = ts.rats().iter().map(|r| r.len()).max().unwrap_or(0);
eprintln!(
" Parsed: ring={}, tiles={}, rats={}, subseqs={}, k={}",
coll.ring,
coll.tile_angles.len(),
coll.provenances.len(),
coll.subseqs.len(),
k,
);
eprintln!(" Replaying glues...");
let t1 = Instant::now();
let rats = coll.replay_rats(&ts)?;
eprintln!(" Replayed {} rats in {:.2?}", rats.len(), t1.elapsed());
eprintln!(" Checking subseq presence...");
let t2 = Instant::now();
let presence_errors = coll.presence_errors(&rats);
for (rat_id, seq) in presence_errors.iter().take(5) {
eprintln!(
" ERROR: subseq {:?} not found in rat {} (len={})",
seq,
rat_id,
rats[*rat_id].len()
);
}
if !presence_errors.is_empty() {
return Err(format!("{} subseq presence errors", presence_errors.len()));
}
eprintln!(
" All {} subseqs verified present in {:.2?}",
coll.subseqs.len(),
t2.elapsed(),
);
eprintln!(" Checking completeness (witnesses x tiles)...");
let t3 = Instant::now();
let witness_ids: Vec<usize> = {
let mut ids: Vec<usize> = coll.subseqs.iter().map(|(id, _)| *id).collect();
ids.sort_unstable();
ids.dedup();
ids
};
let known: std::collections::BTreeSet<Vec<i8>> =
coll.subseqs.iter().map(|(_, s)| s.clone()).collect();
let report = check_fixed_point(&ts, &rats, &witness_ids, &known, k);
for sub in report.missing.iter().take(10) {
eprintln!(" MISSING: subseq {:?} not in collection", sub);
}
eprintln!(
" Completeness: {} matches checked, {} new subseqs found in {:.2?}",
report.matches_checked,
report.missing.len(),
t3.elapsed(),
);
if !report.is_complete() {
return Err(format!(
"Completeness check FAILED: {} new subseqs not in collection",
report.missing.len()
));
}
eprintln!(" Validation PASSED in {:.2?}", t0.elapsed());
Ok(())
}
fn run_collect_ring<T: IsRing>(
ts: Arc<TileSet<T>>,
kind: CollectKind,
ring: &str,
label: &str,
) -> serde_json::Value {
match kind {
CollectKind::Nbhd => serde_json::to_value(collect_nbhd(ts, ring, label)).unwrap(),
CollectKind::Jtype => serde_json::to_value(collect_jtype(ts, ring, label)).unwrap(),
CollectKind::Seq => serde_json::to_value(collect_seq(ts, ring, label)).unwrap(),
}
}
pub fn run_collect(tileset: TileSetKind, kind: CollectKind) -> serde_json::Value {
let label = tileset.label();
match tileset {
TileSetKind::Penrose => run_collect_ring(ts_zz10(tileset), kind, "ZZ10", label),
_ => run_collect_ring(ts_zz12(tileset), kind, "ZZ12", label),
}
}
pub fn run_validate(env: Envelope) -> Result<(), String> {
let ring = env
.payload
.get("ring")
.and_then(|v| v.as_str())
.ok_or("payload missing `ring` field")?
.to_string();
let tile_angles: Vec<Vec<i8>> = env
.payload
.get("tile_angles")
.ok_or("payload missing `tile_angles`")?
.clone()
.pipe(serde_json::from_value)
.map_err(|e| e.to_string())?;
match ring.as_str() {
"ZZ12" => {
let ts = rebuild_tileset_zz12(&tile_angles);
dispatch_validate(env.kind, env.payload, ts)
}
"ZZ10" => {
let ts = rebuild_tileset_zz10(&tile_angles);
dispatch_validate(env.kind, env.payload, ts)
}
other => Err(format!("unsupported ring: {other}")),
}
}
fn dispatch_validate<T: IsRing>(
kind: CollectKind,
payload: serde_json::Value,
ts: Arc<TileSet<T>>,
) -> Result<(), String> {
match kind {
CollectKind::Nbhd => {
let coll: neighborhood::Collection =
serde_json::from_value(payload).map_err(|e| e.to_string())?;
validate_nbhd(coll, ts)
}
CollectKind::Jtype => {
let coll: junctiontypes::Collection =
serde_json::from_value(payload).map_err(|e| e.to_string())?;
validate_jtype(coll, ts)
}
CollectKind::Seq => {
let coll: seq_explorer::Collection =
serde_json::from_value(payload).map_err(|e| e.to_string())?;
validate_seq(coll, ts)
}
}
}
trait Pipe: Sized {
fn pipe<R>(self, f: impl FnOnce(Self) -> R) -> R {
f(self)
}
}
impl<T> Pipe for T {}