use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::cyclotomic::{IsRing, Units};
use crate::geom::rat::Rat;
use crate::geom::snake::Snake;
use crate::rat_enum::canonical::CanonicalOps;
use crate::rat_enum::prune::Prunes;
use crate::rat_enum::stats::DfsStats;
pub static STREAM_RAT_LINES: AtomicBool = AtomicBool::new(true);
pub fn rat_enum_with<ZZ: IsRing>(
max_steps: usize,
step: i8,
ops: CanonicalOps,
label: &str,
prefix: &str,
paranoid: bool,
prunes: &Prunes,
) -> (Vec<Vec<i8>>, DfsStats) {
let mut result: HashSet<Vec<i8>> = HashSet::new();
let mut snake: Snake<ZZ> = Snake::new();
let mut stats = DfsStats::default();
println!("-------- {label} started --------");
if paranoid {
println!("paranoid: per-step fresh-snake cross-check enabled");
}
{
let mut record = hashset_recorder(&mut result);
rat_enum_step::<ZZ>(
&mut snake,
max_steps,
step,
&mut record,
&mut stats,
ops,
paranoid,
prunes,
);
}
println!(
"-------- {label} completed --------\n{prefix}{} rats found",
result.len()
);
let mut result: Vec<Vec<i8>> = result.into_iter().collect();
result.sort_by_key(|x| x.len());
(result, stats)
}
pub fn hashset_recorder<'a>(set: &'a mut HashSet<Vec<i8>>) -> impl FnMut(&[i8]) + 'a {
move |seq: &[i8]| {
if set.insert(seq.to_vec()) && STREAM_RAT_LINES.load(Ordering::Relaxed) {
println!("RAT {seq:?}");
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn rat_enum_step<ZZ: IsRing>(
snake: &mut Snake<ZZ>,
max_steps: usize,
step: i8,
record: &mut dyn FnMut(&[i8]),
stats: &mut DfsStats,
ops: CanonicalOps,
paranoid: bool,
prunes: &Prunes,
) {
let depth = snake.angles().len();
if depth >= max_steps {
return;
}
let remaining = (max_steps - depth) as i64;
for direction in ((-ZZ::hturn() + 1)..ZZ::hturn()).rev() {
if direction.rem_euclid(step) != 0 {
continue;
}
if !(ops.is_canonical)(snake.angles(), direction) {
stats.canonical_skip += 1;
continue;
}
let new_pt = snake.offset()
+ <ZZ as Units>::unit(snake.direction()) * <ZZ as Units>::unit(direction);
if !new_pt.is_zero() && !new_pt.within_radius(remaining) {
stats.too_far += 1;
continue;
}
if let Some(sp) = prunes.shadow_prune.as_deref()
&& !new_pt.is_zero()
&& !sp.allows_closure(&new_pt, remaining)
{
stats.shadow_skip += 1;
continue;
}
let remaining_after = (remaining as usize).saturating_sub(1);
if let Some(mp) = prunes.modular_prune.as_deref()
&& !mp.allows_closure(new_pt.int_coeffs_slice(), remaining_after)
{
stats.modular_skip += 1;
continue;
}
if let Some(ck) = prunes.closure_table_prune.as_deref()
&& remaining_after <= ck.max_l
{
let turn = ZZ::turn();
let new_facing = (snake.direction() + direction).rem_euclid(turn);
let neg_facing = (-new_facing).rem_euclid(turn);
let target: ZZ = -(<ZZ as Units>::unit(neg_facing) * new_pt);
let key = (target.int_coeffs_slice().to_vec(), neg_facing);
if !ck.keys.contains(&key) {
stats.closure_table_skip += 1;
continue;
}
}
if !snake.add(direction) {
stats.intersected += 1;
continue;
}
if paranoid {
let angles = snake.angles().to_vec();
let mut fresh: Snake<ZZ> = Snake::new();
for (i, &a) in angles.iter().enumerate() {
assert!(
fresh.add(a),
"stateful snake accepted full prefix {:?} but fresh snake \
rejected angle {} at step {}",
&angles,
a,
i
);
}
assert_eq!(
fresh.is_closed(),
snake.is_closed(),
"fresh snake disagrees on is_closed for {:?}",
&angles
);
}
if snake.is_closed() {
stats.closed += 1;
let r = {
let tmp = Rat::from_unchecked(snake);
if tmp.chirality() > 0 {
tmp
} else {
tmp.reversed()
}
.canonical()
};
let seq = (ops.canonicalize)(r.seq());
record(&seq);
} else {
stats.recursed += 1;
rat_enum_step::<ZZ>(snake, max_steps, step, record, stats, ops, paranoid, prunes);
}
snake.pop();
}
}
pub struct SeedGather<'a> {
pub seeds: &'a mut Vec<Vec<i8>>,
pub record_closed: &'a mut dyn FnMut(&[i8]),
pub stats: &'a mut DfsStats,
}
#[allow(clippy::too_many_arguments)]
pub fn collect_seeds<ZZ: IsRing>(
snake: &mut Snake<ZZ>,
max_steps: usize,
step: i8,
split_depth: usize,
gather: &mut SeedGather<'_>,
ops: CanonicalOps,
paranoid: bool,
prunes: &Prunes,
) {
let depth = snake.angles().len();
if depth >= max_steps {
return;
}
let remaining = (max_steps - depth) as i64;
for direction in ((-ZZ::hturn() + 1)..ZZ::hturn()).rev() {
if direction.rem_euclid(step) != 0 {
continue;
}
if !(ops.is_canonical)(snake.angles(), direction) {
gather.stats.canonical_skip += 1;
continue;
}
let new_pt = snake.offset()
+ <ZZ as Units>::unit(snake.direction()) * <ZZ as Units>::unit(direction);
if !new_pt.is_zero() && !new_pt.within_radius(remaining) {
gather.stats.too_far += 1;
continue;
}
if let Some(sp) = prunes.shadow_prune.as_deref()
&& !new_pt.is_zero()
&& !sp.allows_closure(&new_pt, remaining)
{
gather.stats.shadow_skip += 1;
continue;
}
let remaining_after = (remaining as usize).saturating_sub(1);
if let Some(mp) = prunes.modular_prune.as_deref()
&& !mp.allows_closure(new_pt.int_coeffs_slice(), remaining_after)
{
gather.stats.modular_skip += 1;
continue;
}
if let Some(ck) = prunes.closure_table_prune.as_deref()
&& remaining_after <= ck.max_l
{
let turn = ZZ::turn();
let new_facing = (snake.direction() + direction).rem_euclid(turn);
let neg_facing = (-new_facing).rem_euclid(turn);
let target: ZZ = -(<ZZ as Units>::unit(neg_facing) * new_pt);
let key = (target.int_coeffs_slice().to_vec(), neg_facing);
if !ck.keys.contains(&key) {
gather.stats.closure_table_skip += 1;
continue;
}
}
if !snake.add(direction) {
gather.stats.intersected += 1;
continue;
}
if paranoid {
let angles = snake.angles().to_vec();
let mut fresh: Snake<ZZ> = Snake::new();
for (i, &a) in angles.iter().enumerate() {
assert!(
fresh.add(a),
"stateful snake accepted full prefix {:?} but fresh snake \
rejected angle {} at step {}",
&angles,
a,
i
);
}
assert_eq!(
fresh.is_closed(),
snake.is_closed(),
"fresh snake disagrees on is_closed for {:?}",
&angles
);
}
if snake.is_closed() {
gather.stats.closed += 1;
let r = {
let tmp = Rat::from_unchecked(snake);
if tmp.chirality() > 0 {
tmp
} else {
tmp.reversed()
}
.canonical()
};
let seq = (ops.canonicalize)(r.seq());
(gather.record_closed)(&seq);
} else {
gather.stats.recursed += 1;
if snake.angles().len() >= split_depth {
gather.seeds.push(snake.angles().to_vec());
} else {
collect_seeds::<ZZ>(
snake,
max_steps,
step,
split_depth,
gather,
ops,
paranoid,
prunes,
);
}
}
snake.pop();
}
}