use alloc::vec::Vec;
use crate::tower::TowerLevel;
use crate::{BinaryField128, clmul, poly_slice};
pub const HAS_HARDWARE_CLMUL: bool = clmul::HAS_HARDWARE_CLMUL;
#[must_use]
#[inline]
pub fn from_tower(x: BinaryField128) -> u128 {
clmul::tower_to_poly_128(x.to_repr())
}
#[inline]
pub fn to_tower(v: u128) -> BinaryField128 {
BinaryField128::from_repr(clmul::poly_to_tower_128(v))
}
#[inline]
pub fn from_tower_slice(values: &mut [u128]) {
clmul::tower_to_poly_128_slice(values);
}
#[inline]
pub fn to_tower_slice(values: &mut [u128]) {
clmul::poly_to_tower_128_slice(values);
}
#[allow(clippy::missing_const_for_fn)]
#[must_use]
#[inline]
pub fn mul(a: u128, b: u128) -> u128 {
clmul::poly_mul_128(a, b)
}
#[allow(clippy::missing_const_for_fn)]
#[must_use]
#[inline]
pub fn square(a: u128) -> u128 {
clmul::poly_square_128(a)
}
#[inline]
pub fn mul_slice(values: &mut [u128], scalar: u128) {
if let [value] = values {
*value = mul(*value, scalar);
return;
}
poly_slice::scale(values, scalar);
}
#[inline]
pub fn butterfly_forward(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
if scalar == 0 {
for (lo, hi) in lo.iter().zip(hi) {
*hi ^= *lo;
}
return;
}
if lo.len() == 1 {
lo[0] ^= mul(scalar, hi[0]);
hi[0] ^= lo[0];
return;
}
poly_slice::butterfly_forward(lo, hi, scalar);
}
#[inline]
pub fn butterfly_inverse(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
if scalar == 0 {
for (lo, hi) in lo.iter().zip(hi) {
*hi ^= *lo;
}
return;
}
if lo.len() == 1 {
hi[0] ^= lo[0];
lo[0] ^= mul(scalar, hi[0]);
return;
}
poly_slice::butterfly_inverse(lo, hi, scalar);
}
pub const LOW_STAGES: usize = poly_slice::LOW_STAGES;
type Pair = [u128; 2];
#[inline(always)]
const fn add(a: Pair, b: Pair) -> Pair {
[a[0] ^ b[0], a[1] ^ b[1]]
}
#[derive(Clone, Debug)]
pub struct LowStageTwiddles {
shifts: [Pair; LOW_STAGES],
prefix: Vec<Pair>,
steps: Vec<[Pair; LOW_STAGES]>,
}
impl LowStageTwiddles {
pub fn new(shifts: &[u128], basis: &[u128]) -> Self {
assert!(
basis.len() >= LOW_STAGES - 1,
"the low stages need at least three basis elements"
);
let pair = |value: u128| [value, clmul::poly_mul_128(value, 1 << 64)];
let prefix: Vec<Pair> = core::iter::once(0)
.chain(basis.iter().scan(0, |sum, &element| {
*sum ^= element;
Some(*sum)
}))
.map(pair)
.collect();
let steps = (0..(basis.len() + 1).saturating_sub(LOW_STAGES))
.map(|trailing| {
core::array::from_fn(|j| {
let from = LOW_STAGES - 1 - j;
add(prefix[from + trailing + 1], prefix[from])
})
})
.collect();
Self {
shifts: core::array::from_fn(|j| pair(shifts[j])),
prefix,
steps,
}
}
pub fn forward(&self, values: &mut [u128], first_run: usize) {
assert_eq!(
values.len() % (1 << LOW_STAGES),
0,
"the low stages run over whole runs"
);
poly_slice::forward_low_stages(values, first_run, self);
}
#[inline(always)]
pub(crate) fn span(&self, mut bits: usize, from: usize) -> Pair {
let mut sum = [0; 2];
while bits != 0 {
let k = from + bits.trailing_zeros() as usize;
sum = add(sum, add(self.prefix[k + 1], self.prefix[k]));
bits &= bits - 1;
}
sum
}
#[inline(always)]
pub(crate) fn run_twiddles(&self, run: usize) -> [Pair; LOW_STAGES] {
core::array::from_fn(|j| add(self.shifts[j], self.span(run, LOW_STAGES - 1 - j)))
}
#[inline(always)]
pub(crate) fn step(&self, run: usize) -> &[Pair; LOW_STAGES] {
&self.steps[run.trailing_ones() as usize]
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;
use super::{LOW_STAGES, LowStageTwiddles, from_tower, mul, square, to_tower};
use crate::BinaryField128;
const LOW_RUN: usize = 1 << LOW_STAGES;
const LOW_BASIS: usize = 40;
fn element(bits: u128) -> BinaryField128 {
BinaryField128::from_le_bytes(bits.to_le_bytes())
}
fn reference_low_stages(
values: &mut [u128],
first_run: usize,
shifts: &[u128],
basis: &[u128],
) {
let first = first_run << LOW_STAGES;
for j in (0..LOW_STAGES).rev() {
let half = 1 << j;
for (index, pair) in values.chunks_exact_mut(2 * half).enumerate() {
let block = (first >> (j + 1)) + index;
let twiddle = (0..basis.len())
.filter(|k| (block >> k) & 1 == 1)
.fold(shifts[j], |twiddle, k| twiddle ^ basis[k]);
let (lo, hi) = pair.split_at_mut(half);
for (lo, hi) in lo.iter_mut().zip(hi) {
*lo ^= mul(twiddle, *hi);
*hi ^= *lo;
}
}
}
}
fn run_index() -> impl Strategy<Value = usize> {
prop_oneof![
0usize..64,
Just((1 << 20) - 1),
Just((1 << 24) - 2),
0usize..1 << 24,
]
}
proptest! {
#[test]
fn the_low_stages_match_one_stage_at_a_time(
shifts in any::<[u128; LOW_STAGES]>(),
basis in prop::collection::vec(any::<u128>(), LOW_BASIS),
values in prop::collection::vec(any::<u128>(), 5 * LOW_RUN),
runs in 0usize..=5,
first_run in run_index(),
) {
let mut actual: Vec<u128> = values[..runs * LOW_RUN].to_vec();
let mut expected = actual.clone();
reference_low_stages(&mut expected, first_run, &shifts, &basis);
LowStageTwiddles::new(&shifts, &basis).forward(&mut actual, first_run);
prop_assert_eq!(actual, expected);
}
#[test]
fn the_change_of_basis_round_trips(bits in any::<u128>()) {
let x = element(bits);
prop_assert_eq!(to_tower(from_tower(x)), x);
}
#[test]
fn the_change_of_basis_is_additive(a in any::<u128>(), b in any::<u128>()) {
let (x, y) = (element(a), element(b));
prop_assert_eq!(from_tower(x + y), from_tower(x) ^ from_tower(y));
}
#[test]
fn the_product_agrees_with_the_tower_product(a in any::<u128>(), b in any::<u128>()) {
let (x, y) = (element(a), element(b));
prop_assert_eq!(to_tower(mul(from_tower(x), from_tower(y))), x * y);
}
#[test]
fn the_square_agrees_with_the_tower_square(a in any::<u128>()) {
let x = element(a);
prop_assert_eq!(to_tower(square(from_tower(x))), x.square());
}
}
#[test]
fn slice_operations_match_reference_tower_arithmetic() {
use alloc::vec::Vec;
for len in [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 64, 257] {
for scalar in [
0,
1,
0x87,
1 << 127,
0xfeed_9876_0123_4567_89ab_cdef_9876_5432,
] {
let t = element(scalar);
let xs: Vec<_> = (0..len)
.map(|i| {
element(
(i as u128 + 1).wrapping_mul(0xfeed_8765_dead_beef_cafe_0123_4567_89ab),
)
})
.collect();
let ys: Vec<_> = xs.iter().rev().copied().collect();
let mut lo: Vec<_> = xs.iter().copied().map(from_tower).collect();
let mut hi: Vec<_> = ys.iter().copied().map(from_tower).collect();
let original = (lo.clone(), hi.clone());
super::butterfly_forward(&mut lo, &mut hi, from_tower(t));
for i in 0..len {
let expected = xs[i] + ys[i].reference_mul(t);
assert_eq!(to_tower(lo[i]), expected);
assert_eq!(to_tower(hi[i]), expected + ys[i]);
}
super::butterfly_inverse(&mut lo, &mut hi, from_tower(t));
assert_eq!((&lo, &hi), (&original.0, &original.1));
super::mul_slice(&mut lo, from_tower(t));
for i in 0..len {
assert_eq!(to_tower(lo[i]), xs[i].reference_mul(t));
}
}
}
}
#[test]
#[should_panic = "the low stages run over whole runs"]
fn the_low_stages_reject_a_partial_run() {
let twiddles = LowStageTwiddles::new(&[1; LOW_STAGES], &[1; LOW_BASIS]);
twiddles.forward(&mut [0; LOW_RUN + 1], 0);
}
#[test]
#[should_panic(expected = "the low stages need at least three basis elements")]
fn the_low_stages_reject_a_short_basis() {
LowStageTwiddles::new(&[1; LOW_STAGES], &[1; LOW_STAGES - 2]);
}
#[test]
#[should_panic = "butterfly lengths differ"]
fn forward_rejects_mismatched_lengths() {
super::butterfly_forward(&mut [0], &mut [], 0);
}
#[test]
#[should_panic = "butterfly lengths differ"]
fn inverse_rejects_mismatched_lengths() {
super::butterfly_inverse(&mut [], &mut [0], 1);
}
#[test]
fn the_change_of_basis_fixes_the_constants() {
assert_eq!(from_tower(BinaryField128::ZERO), 0);
assert_eq!(from_tower(BinaryField128::ONE), 1);
assert_eq!(to_tower(0), BinaryField128::ZERO);
assert_eq!(to_tower(1), BinaryField128::ONE);
}
}