use crate::projection::Projection;
pub const LANES: usize = 16;
pub const MAX_CONJUNCTS: usize = 2;
const MAX_CLOSURE_STEPS: u64 = 1_500_000;
const MAX_CANDIDATES: usize = 64;
#[derive(Clone)]
pub struct Quotient {
pub blocks: u8,
pub threshold: u8,
pub start: u8,
pub rows: [[u8; LANES]; 256],
}
struct Forest {
parent: Vec<u16>,
}
impl Forest {
fn new(n: usize) -> Self {
let mut forest = Self { parent: vec![0; n] };
forest.reset();
forest
}
fn reset(&mut self) {
for (p, i) in self.parent.iter_mut().zip(0u16..) {
*p = i;
}
}
fn find(&mut self, mut x: u16) -> u16 {
while self.parent[usize::from(x)] != x {
let grand = self.parent[usize::from(self.parent[usize::from(x)])];
self.parent[usize::from(x)] = grand;
x = grand;
}
x
}
fn join(&mut self, a: u16, b: u16) -> bool {
let (ra, rb) = (self.find(a), self.find(b));
if ra == rb {
return false;
}
self.parent[usize::from(ra)] = rb;
true
}
}
pub fn harvest(core: &Projection) -> Vec<Quotient> {
let mut forest = Forest::new(core.states);
let mut steps = 0u64;
let mut kept: Vec<(Vec<u8>, f32)> = Vec::new();
let states = core.states as u16;
'sweep: for p in 0..states {
for q in (p + 1)..states {
let Some(block) = close(core, &mut forest, &mut steps, (p, q)) else {
break 'sweep; };
let blocks = block_count(&block);
if blocks <= 1 || usize::from(blocks) > LANES {
continue;
}
let Some(quotient) = induce(core, &block, blocks) else {
continue;
};
if kept.iter().any(|(seen, _)| *seen == block) {
continue;
}
let accepting = f32::from(blocks - quotient.threshold);
kept.push((
block,
accepting / f32::from(blocks) - f32::from(blocks) * 1e-4,
));
if kept.len() == MAX_CANDIDATES {
break 'sweep;
}
}
}
select(core, kept)
}
fn select(core: &Projection, mut kept: Vec<(Vec<u8>, f32)>) -> Vec<Quotient> {
kept.sort_by(|a, b| a.1.total_cmp(&b.1));
let mut chosen: Vec<Vec<u8>> = Vec::new();
let mut out: Vec<Quotient> = Vec::new();
for (block, _) in kept {
if out.len() == MAX_CONJUNCTS {
break;
}
if chosen.iter().any(|c| refines(c, &block)) {
continue;
}
if let Some(q) = induce(core, &block, block_count(&block)) {
out.push(q);
chosen.push(block);
}
}
out
}
fn close(
core: &Projection,
forest: &mut Forest,
steps: &mut u64,
pair: (u16, u16),
) -> Option<Vec<u8>> {
forest.reset();
let mut work = vec![pair];
while let Some((a, b)) = work.pop() {
if !forest.join(a, b) {
continue;
}
*steps += core.classes as u64;
if *steps > MAX_CLOSURE_STEPS {
return None;
}
for k in 0..core.classes {
let (x, y) = (core.step(a, k), core.step(b, k));
if forest.find(x) != forest.find(y) {
work.push((x, y));
}
}
}
let mut canon = vec![u8::MAX; core.states];
let mut block = vec![0u8; core.states];
let mut blocks = 0u8;
for (slot, i) in block.iter_mut().zip(0u16..) {
let root = usize::from(forest.find(i));
if canon[root] == u8::MAX {
canon[root] = blocks;
blocks += 1;
}
*slot = canon[root];
}
Some(block)
}
fn refines(fine: &[u8], coarse: &[u8]) -> bool {
let mut seen = [i16::MIN; LANES];
for (&a, &b) in fine.iter().zip(coarse) {
let slot = &mut seen[usize::from(a)];
if *slot == i16::MIN {
*slot = i16::from(b);
} else if *slot != i16::from(b) {
return false;
}
}
true
}
fn block_count(block: &[u8]) -> u8 {
block.iter().copied().max().unwrap_or(0).saturating_add(1)
}
fn induce(core: &Projection, block: &[u8], blocks: u8) -> Option<Quotient> {
if usize::from(blocks) > LANES {
return None;
}
let mut block_accepts = [false; LANES];
for (&acc, &b) in core.accept.iter().zip(block) {
if acc {
block_accepts[usize::from(b)] = true;
}
}
let nb = usize::from(blocks);
let threshold = block_accepts[..nb].iter().filter(|&&a| !a).count() as u8;
if threshold == 0 || threshold == blocks {
return None; }
let mut relabel = [0u8; LANES];
let (mut lo, mut hi) = (0u8, threshold);
for (slot, &acc) in relabel.iter_mut().zip(&block_accepts[..nb]) {
let next = if acc { &mut hi } else { &mut lo };
*slot = *next;
*next += 1;
}
let start = relabel[usize::from(block[usize::from(core.start)])];
if start >= threshold {
return None; }
let mut table = [[0u8; 256]; LANES];
let mut filled = [false; LANES];
for (&b, i) in block.iter().zip(0u16..) {
let from = usize::from(relabel[usize::from(b)]);
for k in 0..core.classes {
let to = relabel[usize::from(block[usize::from(core.step(i, k))])];
if filled[from] {
if table[from][k] != to {
return None; }
} else {
table[from][k] = to;
}
}
filled[from] = true;
}
if filled[..nb].iter().any(|&f| !f) {
return None; }
let mut rows = [[0u8; LANES]; 256];
for (row, &k) in rows.iter_mut().zip(&core.class_of) {
for (lane, state) in row.iter_mut().zip(&table[..nb]) {
*lane = state[usize::from(k)];
}
}
Some(Quotient {
blocks,
threshold,
start,
rows,
})
}