use crate::{BinaryFieldExtras, Flat, HardwareField};
const MAX_DIM: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CantorError {
BadDim {
dim: usize,
max: usize,
},
ChainEnds {
at: usize,
},
BadCoeffLength {
got: usize,
},
IndexOutOfRange {
index: usize,
dim: usize,
},
UnsortedIndices {
at: usize,
},
ShortScratch {
need: usize,
got: usize,
},
BadOutLength {
expected: usize,
got: usize,
},
}
impl core::fmt::Display for CantorError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CantorError::BadDim { dim, max } => {
write!(f, "CantorBasis dimension {dim}, expected 1..={max}")
}
CantorError::ChainEnds { at } => {
write!(f, "CantorBasis chain has no element {at}")
}
CantorError::BadCoeffLength { got } => {
write!(
f,
"CantorBasis coefficient count {got} is not a power of two"
)
}
CantorError::IndexOutOfRange { index, dim } => {
write!(
f,
"CantorBasis index {index} needs more than {dim} basis elements"
)
}
CantorError::UnsortedIndices { at } => {
write!(f, "CantorBasis indices decrease at position {at}")
}
CantorError::ShortScratch { need, got } => {
write!(f, "CantorBasis scratch length {got}, needs {need}")
}
CantorError::BadOutLength { expected, got } => {
write!(f, "CantorBasis output length {got}, expected {expected}")
}
}
}
}
impl core::error::Error for CantorError {}
#[derive(Clone)]
pub struct CantorBasis<F> {
dim: usize,
betas: [Flat<F>; MAX_DIM],
}
impl<F: BinaryFieldExtras + HardwareField> CantorBasis<F> {
pub fn new(dim: usize) -> Result<Self, CantorError> {
let max = F::BITS.min(MAX_DIM);
if dim == 0 || dim > max {
return Err(CantorError::BadDim { dim, max });
}
let mut betas = [Flat::from_raw(F::ZERO); MAX_DIM];
let mut beta = F::ONE;
betas[0] = beta.to_hardware();
for (at, slot) in betas.iter_mut().enumerate().take(dim).skip(1) {
beta = F::solve_quadratic(beta).ok_or(CantorError::ChainEnds { at })?;
*slot = beta.to_hardware();
}
Ok(Self { dim, betas })
}
pub fn betas(&self) -> &[Flat<F>] {
&self.betas[..self.dim]
}
pub fn point(&self, index: usize) -> Result<Flat<F>, CantorError> {
self.check_index(index)?;
Ok(self.beta_sum(index, 0))
}
pub fn evaluate_at(
&self,
coeffs: &[Flat<F>],
shift: Flat<F>,
indices: &[usize],
scratch: &mut [Flat<F>],
out: &mut [Flat<F>],
) -> Result<(), CantorError> {
let k = coeffs.len();
if !k.is_power_of_two() {
return Err(CantorError::BadCoeffLength { got: k });
}
if out.len() != indices.len() {
return Err(CantorError::BadOutLength {
expected: indices.len(),
got: out.len(),
});
}
if scratch.len() < k - 1 {
return Err(CantorError::ShortScratch {
need: k - 1,
got: scratch.len(),
});
}
if let Some(at) = indices.windows(2).position(|w| w[1] < w[0]) {
return Err(CantorError::UnsortedIndices { at: at + 1 });
}
if let Some(&last) = indices.last() {
self.check_index(last)?;
}
let log_k = k.trailing_zeros() as usize;
if log_k == 0 {
out.fill(coeffs[0]);
return Ok(());
}
let mut cosets = [Flat::from_raw(F::ZERO); MAX_DIM];
let mut c = shift;
for slot in cosets.iter_mut().take(log_k) {
*slot = c;
c = c * c + c;
}
let mut rest = indices;
let mut rest_out = out;
while let Some(&first) = rest.first() {
let class = first >> log_k;
let len = rest.partition_point(|&j| j >> log_k == class);
let (head, tail) = rest.split_at(len);
let (head_out, tail_out) = core::mem::take(&mut rest_out).split_at_mut(len);
self.fold(coeffs, log_k - 1, head, &cosets, scratch, head_out);
rest = tail;
rest_out = tail_out;
}
Ok(())
}
fn check_index(&self, index: usize) -> Result<(), CantorError> {
match index.checked_shr(self.dim as u32) {
Some(high) if high != 0 => Err(CantorError::IndexOutOfRange {
index,
dim: self.dim,
}),
_ => Ok(()),
}
}
fn beta_sum(&self, bits: usize, first: usize) -> Flat<F> {
let mut acc = Flat::from_raw(F::ZERO);
let mut rest = bits;
while rest != 0 {
acc += self.betas[first + rest.trailing_zeros() as usize];
rest &= rest - 1;
}
acc
}
fn fold(
&self,
a: &[Flat<F>],
level: usize,
indices: &[usize],
cosets: &[Flat<F>],
scratch: &mut [Flat<F>],
out: &mut [Flat<F>],
) {
let s = 1usize << level;
let (lo, hi) = a.split_at(s);
let split = indices.partition_point(|&j| (j >> level) & 1 == 0);
let (left, right) = indices.split_at(split);
let (left_out, right_out) = out.split_at_mut(split);
let mut tw = cosets[level] + self.beta_sum(indices[0] >> (level + 1), 1);
if left.is_empty() {
tw += self.betas[0];
}
let (below, mine) = scratch.split_at_mut(s - 1);
let buf = &mut mine[..s];
for ((slot, &p), &q) in buf.iter_mut().zip(lo).zip(hi) {
*slot = p + q * tw;
}
if level == 0 {
let right_value = if left.is_empty() {
buf[0]
} else {
buf[0] + hi[0]
};
left_out.fill(buf[0]);
right_out.fill(right_value);
return;
}
if !left.is_empty() {
self.fold(buf, level - 1, left, cosets, below, left_out);
if right.is_empty() {
return;
}
for (slot, &q) in buf.iter_mut().zip(hi) {
*slot += q;
}
}
self.fold(buf, level - 1, right, cosets, below, right_out);
}
}