use lens::rspcs::field::eq_table;
use lens::rspcs::whir::LeafLayout;
use nebu::{Fp3, Goldilocks};
use super::circuit::trace::Pre;
use super::decide::KeyWords;
use super::ops::{Arith, Ops};
use super::word::{Arity, Group, Word};
pub const WORD: usize = 64;
pub const BITS: usize = 8;
pub fn columns(ext: &[bool]) -> Vec<(u16, u8)> {
let mut out: Vec<(u16, u8)> = (0..ext.len()).map(|c| (c as u16, 0)).collect();
for (c, &e) in ext.iter().enumerate() {
if e {
out.push((c as u16, 1));
out.push((c as u16, 2));
}
}
assert!(
out.len() <= WORD << (BITS - 6),
"split key: at most four words"
);
out
}
pub fn ext_columns(sparse: &[Vec<(u32, Fp3)>]) -> Vec<bool> {
sparse
.iter()
.map(|c| {
c.iter()
.any(|&(_, v)| v.c1 != Goldilocks::ZERO || v.c2 != Goldilocks::ZERO)
})
.collect()
}
pub fn words(phys: usize) -> usize {
phys.div_ceil(WORD)
}
fn limb(v: Fp3, l: u8) -> Goldilocks {
match l {
0 => v.c0,
1 => v.c1,
_ => v.c2,
}
}
pub fn commit(
layout: LeafLayout,
n: usize,
pre: &Pre,
phys: &[(u16, u8)],
arity: Arity,
) -> KeyWords {
let rows = 1usize << n;
let words: Vec<Word> = (0..words(phys.len()))
.map(|w| {
let mut t = vec![Goldilocks::ZERO; WORD * rows];
for j in 0..WORD {
if let Some(&(c, l)) = phys.get(WORD * w + j) {
for (r, &v) in pre.cols[c as usize].iter().enumerate() {
t[j * rows + r] = limb(v, l);
}
}
}
Word::member_base_streamed(layout, &t)
})
.collect();
let group = Group::new_cached(words, arity);
let root = group.root();
KeyWords { group, root }
}
pub fn at(sparse: &[Vec<(u32, Fp3)>], phys: &[(u16, u8)], rho: &[Fp3]) -> Vec<Fp3> {
let e = eq_table(rho);
phys.iter()
.map(|&(c, l)| {
sparse[c as usize].iter().fold(Fp3::ZERO, |a, &(x, v)| {
a + lens::rspcs::field::mul_base(e[x as usize], limb(v, l))
})
})
.collect()
}
pub fn logical<O: Ops>(o: &mut O, phys: &[(u16, u8)], vals: &[O::V], count: usize) -> Vec<O::V> {
let t = Fp3::new(Goldilocks::ZERO, Goldilocks::ONE, Goldilocks::ZERO);
let mut out: Vec<Option<O::V>> = vec![None; count];
for (&(c, l), &v) in phys.iter().zip(vals) {
let c = c as usize;
out[c] = Some(match (out[c], l) {
(None, 0) => v,
(Some(acc), 1) => o.scale_add(t, v, acc),
(Some(acc), _) => o.scale_add(t * t, v, acc),
_ => panic!("split key: limb 0 first"),
});
}
out.into_iter()
.map(|v| v.expect("every logical column"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::recursion::ops::Native;
#[test]
fn physical_columns_rebuild_the_logical_key_at_any_point() {
let rows = 1usize << 4;
let fp = |a: u64, b: u64, c: u64| {
Fp3::new(Goldilocks::new(a), Goldilocks::new(b), Goldilocks::new(c))
};
let pre = Pre {
cols: (0..70u64)
.map(|c| {
(0..rows as u64)
.map(|r| {
if c % 7 == 3 {
fp(c + r, r * 3 + 1, c ^ r)
} else {
fp(c * r + 2, 0, 0)
}
})
.collect()
})
.collect(),
};
let sparse: Vec<Vec<(u32, Fp3)>> = pre
.cols
.iter()
.map(|c| {
c.iter()
.enumerate()
.filter(|(_, v)| **v != Fp3::ZERO)
.map(|(i, &v)| (i as u32, v))
.collect()
})
.collect();
let phys = columns(&ext_columns(&sparse));
assert_eq!(phys.len(), 70 + 2 * 10);
let rho: Vec<Fp3> = (0..4u64).map(|i| fp(i + 5, 2 * i + 1, 7)).collect();
let pv = at(&sparse, &phys, &rho);
let mut o = Native::new();
let logical = logical(&mut o, &phys, &pv, 70);
let e = eq_table(&rho);
for (c, col) in pre.cols.iter().enumerate() {
let want = col.iter().zip(&e).fold(Fp3::ZERO, |a, (&v, &w)| a + v * w);
assert_eq!(logical[c], want, "column {c}");
}
}
}