1use alloc::vec::Vec;
10
11use crate::tower::TowerLevel;
12use crate::{BinaryField128, clmul, poly_slice};
13
14pub const HAS_HARDWARE_CLMUL: bool = clmul::HAS_HARDWARE_CLMUL;
18
19#[must_use]
21#[inline]
22pub fn from_tower(x: BinaryField128) -> u128 {
23 clmul::tower_to_poly_128(x.to_repr())
24}
25
26#[inline]
28pub fn to_tower(v: u128) -> BinaryField128 {
29 BinaryField128::from_repr(clmul::poly_to_tower_128(v))
30}
31
32#[inline]
48pub fn from_tower_slice(values: &mut [u128]) {
49 clmul::tower_to_poly_128_slice(values);
51}
52
53#[inline]
57pub fn to_tower_slice(values: &mut [u128]) {
58 clmul::poly_to_tower_128_slice(values);
59}
60
61#[allow(clippy::missing_const_for_fn)]
65#[must_use]
66#[inline]
67pub fn mul(a: u128, b: u128) -> u128 {
68 clmul::poly_mul_128(a, b)
69}
70
71#[allow(clippy::missing_const_for_fn)]
73#[must_use]
74#[inline]
75pub fn square(a: u128) -> u128 {
76 clmul::poly_square_128(a)
77}
78
79#[inline]
81pub fn mul_slice(values: &mut [u128], scalar: u128) {
82 if let [value] = values {
84 *value = mul(*value, scalar);
85 return;
86 }
87 poly_slice::scale(values, scalar);
88}
89
90#[inline]
95pub fn butterfly_forward(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
96 assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
97 if scalar == 0 {
99 for (lo, hi) in lo.iter().zip(hi) {
100 *hi ^= *lo;
101 }
102 return;
103 }
104 if lo.len() == 1 {
106 lo[0] ^= mul(scalar, hi[0]);
107 hi[0] ^= lo[0];
108 return;
109 }
110 poly_slice::butterfly_forward(lo, hi, scalar);
111}
112
113#[inline]
118pub fn butterfly_inverse(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
119 assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
120 if scalar == 0 {
122 for (lo, hi) in lo.iter().zip(hi) {
123 *hi ^= *lo;
124 }
125 return;
126 }
127 if lo.len() == 1 {
129 hi[0] ^= lo[0];
130 lo[0] ^= mul(scalar, hi[0]);
131 return;
132 }
133 poly_slice::butterfly_inverse(lo, hi, scalar);
134}
135
136pub const LOW_STAGES: usize = poly_slice::LOW_STAGES;
138
139type Pair = [u128; 2];
143
144#[inline(always)]
146const fn add(a: Pair, b: Pair) -> Pair {
147 [a[0] ^ b[0], a[1] ^ b[1]]
148}
149
150#[derive(Clone, Debug)]
162pub struct LowStageTwiddles {
163 shifts: [Pair; LOW_STAGES],
165 prefix: Vec<Pair>,
167 steps: Vec<[Pair; LOW_STAGES]>,
170}
171
172impl LowStageTwiddles {
173 pub fn new(shifts: &[u128], basis: &[u128]) -> Self {
179 assert!(
180 basis.len() >= LOW_STAGES - 1,
181 "the low stages need at least three basis elements"
182 );
183 let pair = |value: u128| [value, clmul::poly_mul_128(value, 1 << 64)];
184 let prefix: Vec<Pair> = core::iter::once(0)
185 .chain(basis.iter().scan(0, |sum, &element| {
186 *sum ^= element;
187 Some(*sum)
188 }))
189 .map(pair)
190 .collect();
191 let steps = (0..(basis.len() + 1).saturating_sub(LOW_STAGES))
195 .map(|trailing| {
196 core::array::from_fn(|j| {
197 let from = LOW_STAGES - 1 - j;
198 add(prefix[from + trailing + 1], prefix[from])
199 })
200 })
201 .collect();
202 Self {
203 shifts: core::array::from_fn(|j| pair(shifts[j])),
204 prefix,
205 steps,
206 }
207 }
208
209 pub fn forward(&self, values: &mut [u128], first_run: usize) {
217 assert_eq!(
218 values.len() % (1 << LOW_STAGES),
219 0,
220 "the low stages run over whole runs"
221 );
222 poly_slice::forward_low_stages(values, first_run, self);
223 }
224
225 #[inline(always)]
227 pub(crate) fn span(&self, mut bits: usize, from: usize) -> Pair {
228 let mut sum = [0; 2];
229 while bits != 0 {
230 let k = from + bits.trailing_zeros() as usize;
231 sum = add(sum, add(self.prefix[k + 1], self.prefix[k]));
232 bits &= bits - 1;
233 }
234 sum
235 }
236
237 #[inline(always)]
242 pub(crate) fn run_twiddles(&self, run: usize) -> [Pair; LOW_STAGES] {
243 core::array::from_fn(|j| add(self.shifts[j], self.span(run, LOW_STAGES - 1 - j)))
244 }
245
246 #[inline(always)]
251 pub(crate) fn step(&self, run: usize) -> &[Pair; LOW_STAGES] {
252 &self.steps[run.trailing_ones() as usize]
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use alloc::vec::Vec;
259
260 use p3_field::PrimeCharacteristicRing;
261 use proptest::prelude::*;
262
263 use super::{LOW_STAGES, LowStageTwiddles, from_tower, mul, square, to_tower};
264 use crate::BinaryField128;
265
266 const LOW_RUN: usize = 1 << LOW_STAGES;
268
269 const LOW_BASIS: usize = 40;
271
272 fn element(bits: u128) -> BinaryField128 {
274 BinaryField128::from_le_bytes(bits.to_le_bytes())
275 }
276
277 fn reference_low_stages(
280 values: &mut [u128],
281 first_run: usize,
282 shifts: &[u128],
283 basis: &[u128],
284 ) {
285 let first = first_run << LOW_STAGES;
286 for j in (0..LOW_STAGES).rev() {
287 let half = 1 << j;
288 for (index, pair) in values.chunks_exact_mut(2 * half).enumerate() {
289 let block = (first >> (j + 1)) + index;
290 let twiddle = (0..basis.len())
291 .filter(|k| (block >> k) & 1 == 1)
292 .fold(shifts[j], |twiddle, k| twiddle ^ basis[k]);
293 let (lo, hi) = pair.split_at_mut(half);
294 for (lo, hi) in lo.iter_mut().zip(hi) {
295 *lo ^= mul(twiddle, *hi);
296 *hi ^= *lo;
297 }
298 }
299 }
300 }
301
302 fn run_index() -> impl Strategy<Value = usize> {
304 prop_oneof![
305 0usize..64,
306 Just((1 << 20) - 1),
307 Just((1 << 24) - 2),
308 0usize..1 << 24,
309 ]
310 }
311
312 proptest! {
313 #[test]
316 fn the_low_stages_match_one_stage_at_a_time(
317 shifts in any::<[u128; LOW_STAGES]>(),
318 basis in prop::collection::vec(any::<u128>(), LOW_BASIS),
319 values in prop::collection::vec(any::<u128>(), 5 * LOW_RUN),
320 runs in 0usize..=5,
321 first_run in run_index(),
322 ) {
323 let mut actual: Vec<u128> = values[..runs * LOW_RUN].to_vec();
324 let mut expected = actual.clone();
325 reference_low_stages(&mut expected, first_run, &shifts, &basis);
326 LowStageTwiddles::new(&shifts, &basis).forward(&mut actual, first_run);
327 prop_assert_eq!(actual, expected);
328 }
329
330 #[test]
331 fn the_change_of_basis_round_trips(bits in any::<u128>()) {
332 let x = element(bits);
333 prop_assert_eq!(to_tower(from_tower(x)), x);
334 }
335
336 #[test]
338 fn the_change_of_basis_is_additive(a in any::<u128>(), b in any::<u128>()) {
339 let (x, y) = (element(a), element(b));
340 prop_assert_eq!(from_tower(x + y), from_tower(x) ^ from_tower(y));
341 }
342
343 #[test]
345 fn the_product_agrees_with_the_tower_product(a in any::<u128>(), b in any::<u128>()) {
346 let (x, y) = (element(a), element(b));
347 prop_assert_eq!(to_tower(mul(from_tower(x), from_tower(y))), x * y);
348 }
349
350 #[test]
351 fn the_square_agrees_with_the_tower_square(a in any::<u128>()) {
352 let x = element(a);
353 prop_assert_eq!(to_tower(square(from_tower(x))), x.square());
354 }
355 }
356
357 #[test]
358 fn slice_operations_match_reference_tower_arithmetic() {
359 use alloc::vec::Vec;
360 for len in [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 64, 257] {
364 for scalar in [
366 0,
367 1,
368 0x87,
369 1 << 127,
370 0xfeed_9876_0123_4567_89ab_cdef_9876_5432,
371 ] {
372 let t = element(scalar);
373 let xs: Vec<_> = (0..len)
374 .map(|i| {
375 element(
376 (i as u128 + 1).wrapping_mul(0xfeed_8765_dead_beef_cafe_0123_4567_89ab),
377 )
378 })
379 .collect();
380 let ys: Vec<_> = xs.iter().rev().copied().collect();
381 let mut lo: Vec<_> = xs.iter().copied().map(from_tower).collect();
382 let mut hi: Vec<_> = ys.iter().copied().map(from_tower).collect();
383 let original = (lo.clone(), hi.clone());
384 super::butterfly_forward(&mut lo, &mut hi, from_tower(t));
385 for i in 0..len {
386 let expected = xs[i] + ys[i].reference_mul(t);
387 assert_eq!(to_tower(lo[i]), expected);
388 assert_eq!(to_tower(hi[i]), expected + ys[i]);
389 }
390 super::butterfly_inverse(&mut lo, &mut hi, from_tower(t));
391 assert_eq!((&lo, &hi), (&original.0, &original.1));
392 super::mul_slice(&mut lo, from_tower(t));
393 for i in 0..len {
394 assert_eq!(to_tower(lo[i]), xs[i].reference_mul(t));
395 }
396 }
397 }
398 }
399
400 #[test]
401 #[should_panic = "the low stages run over whole runs"]
402 fn the_low_stages_reject_a_partial_run() {
403 let twiddles = LowStageTwiddles::new(&[1; LOW_STAGES], &[1; LOW_BASIS]);
404 twiddles.forward(&mut [0; LOW_RUN + 1], 0);
405 }
406
407 #[test]
408 #[should_panic(expected = "the low stages need at least three basis elements")]
409 fn the_low_stages_reject_a_short_basis() {
410 LowStageTwiddles::new(&[1; LOW_STAGES], &[1; LOW_STAGES - 2]);
411 }
412
413 #[test]
414 #[should_panic = "butterfly lengths differ"]
415 fn forward_rejects_mismatched_lengths() {
416 super::butterfly_forward(&mut [0], &mut [], 0);
417 }
418
419 #[test]
420 #[should_panic = "butterfly lengths differ"]
421 fn inverse_rejects_mismatched_lengths() {
422 super::butterfly_inverse(&mut [], &mut [0], 1);
423 }
424
425 #[test]
427 fn the_change_of_basis_fixes_the_constants() {
428 assert_eq!(from_tower(BinaryField128::ZERO), 0);
429 assert_eq!(from_tower(BinaryField128::ONE), 1);
430 assert_eq!(to_tower(0), BinaryField128::ZERO);
431 assert_eq!(to_tower(1), BinaryField128::ONE);
432 }
433}