1use alloc::vec::Vec;
4
5use p3_binary_field::{BinaryField128, TowerLevel, poly_basis};
6use p3_matrix::Matrix;
7use p3_matrix::dense::RowMajorMatrix;
8use p3_maybe_rayon::prelude::*;
9use p3_util::log2_strict_usize;
10
11use crate::domain::{domain_point, subspace_polynomial};
12use crate::lch::{BUTTERFLY_GRAIN, LchNtt};
13use crate::traits::AdditiveNtt;
14
15#[derive(Clone, Debug, Default)]
27pub struct PolyBasisNtt {
28 tower: LchNtt<BinaryField128>,
29}
30
31#[inline]
33fn twiddle(base: BinaryField128, blk: usize) -> u128 {
34 poly_basis::from_tower(base + domain_point::<BinaryField128>(blk << 1))
35}
36
37impl AdditiveNtt<BinaryField128> for PolyBasisNtt {
38 fn shifted_ntt_batch(
39 &self,
40 mut mat: RowMajorMatrix<BinaryField128>,
41 shift: BinaryField128,
42 ) -> RowMajorMatrix<BinaryField128> {
43 if !poly_basis::HAS_HARDWARE_CLMUL {
44 return self.tower.shifted_ntt_batch(mat, shift);
45 }
46
47 let width = mat.width();
48 let log_n = log2_strict_usize(mat.height());
49 let mut values: Vec<u128> = core::mem::take(&mut mat.values)
53 .into_iter()
54 .map(BinaryField128::to_repr)
55 .collect();
56 values
57 .par_iter_mut()
58 .for_each(|v| *v = poly_basis::from_tower(BinaryField128::from_repr(*v)));
59
60 for j in (0..log_n).rev() {
61 let half = (1 << j) * width;
62 let base = subspace_polynomial::<BinaryField128>(j, shift);
63 values
64 .par_chunks_mut(half << 1)
65 .enumerate()
66 .for_each(|(blk, block)| {
67 let t = twiddle(base, blk);
68 let zero = t == 0;
69 let (lo, hi) = block.split_at_mut(half);
70 let butterfly = |lo: &mut [u128], hi: &mut [u128]| {
71 if zero {
72 for (u, v) in lo.iter_mut().zip(hi) {
73 *v ^= *u;
74 }
75 } else {
76 for (u, v) in lo.iter_mut().zip(hi) {
77 *u ^= poly_basis::mul(t, *v);
78 *v ^= *u;
79 }
80 }
81 };
82 if half <= BUTTERFLY_GRAIN {
83 butterfly(lo, hi);
84 } else {
85 lo.par_chunks_mut(BUTTERFLY_GRAIN)
86 .zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
87 .for_each(|(lo, hi)| butterfly(lo, hi));
88 }
89 });
90 }
91
92 values
93 .par_iter_mut()
94 .for_each(|v| *v = poly_basis::to_tower(*v).to_repr());
95 mat.values = values.into_iter().map(BinaryField128::from_repr).collect();
96 mat
97 }
98
99 fn shifted_intt_batch(
100 &self,
101 mut mat: RowMajorMatrix<BinaryField128>,
102 shift: BinaryField128,
103 ) -> RowMajorMatrix<BinaryField128> {
104 if !poly_basis::HAS_HARDWARE_CLMUL {
105 return self.tower.shifted_intt_batch(mat, shift);
106 }
107
108 let width = mat.width();
109 let log_n = log2_strict_usize(mat.height());
110 let mut values: Vec<u128> = core::mem::take(&mut mat.values)
112 .into_iter()
113 .map(BinaryField128::to_repr)
114 .collect();
115 values
116 .par_iter_mut()
117 .for_each(|v| *v = poly_basis::from_tower(BinaryField128::from_repr(*v)));
118
119 for j in 0..log_n {
120 let half = (1 << j) * width;
121 let base = subspace_polynomial::<BinaryField128>(j, shift);
122 values
123 .par_chunks_mut(half << 1)
124 .enumerate()
125 .for_each(|(blk, block)| {
126 let t = twiddle(base, blk);
127 let zero = t == 0;
128 let (lo, hi) = block.split_at_mut(half);
129 let butterfly = |lo: &mut [u128], hi: &mut [u128]| {
130 if zero {
131 for (u, v) in lo.iter_mut().zip(hi) {
132 *v ^= *u;
133 }
134 } else {
135 for (u, v) in lo.iter_mut().zip(hi) {
136 *v ^= *u;
137 *u ^= poly_basis::mul(t, *v);
138 }
139 }
140 };
141 if half <= BUTTERFLY_GRAIN {
142 butterfly(lo, hi);
143 } else {
144 lo.par_chunks_mut(BUTTERFLY_GRAIN)
145 .zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
146 .for_each(|(lo, hi)| butterfly(lo, hi));
147 }
148 });
149 }
150
151 values
152 .par_iter_mut()
153 .for_each(|v| *v = poly_basis::to_tower(*v).to_repr());
154 mat.values = values.into_iter().map(BinaryField128::from_repr).collect();
155 mat
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use p3_binary_field::{BinaryField128, TowerLevel};
162 use p3_matrix::dense::RowMajorMatrix;
163 use proptest::prelude::*;
164
165 use super::PolyBasisNtt;
166 use crate::naive::NaiveAdditiveNtt;
167 use crate::traits::AdditiveNtt;
168
169 fn matrix(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<BinaryField128> {
171 RowMajorMatrix::new(
172 (0..(width << log_n))
173 .map(|i| {
174 let bits = seed
175 .wrapping_mul(0x9e37_79b9_7f4a_7c15)
176 .wrapping_add(i as u64);
177 BinaryField128::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
178 })
179 .collect(),
180 width,
181 )
182 }
183
184 proptest! {
185 #![proptest_config(ProptestConfig::with_cases(32))]
186
187 #[test]
189 fn poly_basis_matches_naive(
190 log_n in 0usize..=8,
191 width in 1usize..=5,
192 seed in any::<u64>(),
193 shift in any::<u64>(),
194 ) {
195 let coeffs = matrix(log_n, width, seed);
196 let shift = BinaryField128::from_le_byte_iter(
197 shift.to_le_bytes().into_iter().cycle(),
198 );
199
200 let fast = PolyBasisNtt::default().shifted_ntt_batch(coeffs.clone(), shift);
201 let slow = NaiveAdditiveNtt::<BinaryField128>::default()
202 .shifted_ntt_batch(coeffs, shift);
203 prop_assert_eq!(fast, slow);
204 }
205
206 #[test]
207 fn poly_basis_round_trips(
208 log_n in 0usize..=8,
209 width in 1usize..=3,
210 seed in any::<u64>(),
211 shift in any::<u64>(),
212 ) {
213 let coeffs = matrix(log_n, width, seed);
214 let shift = BinaryField128::from_le_byte_iter(
215 shift.to_le_bytes().into_iter().cycle(),
216 );
217 let ntt = PolyBasisNtt::default();
218 let evals = ntt.shifted_ntt_batch(coeffs.clone(), shift);
219 prop_assert_eq!(ntt.shifted_intt_batch(evals, shift), coeffs);
220 }
221
222 #[test]
225 fn poly_basis_lde_matches_naive(
226 log_n in 0usize..=6,
227 added in 0usize..=3,
228 width in 1usize..=3,
229 seed in any::<u64>(),
230 shift in any::<u64>(),
231 ) {
232 let coeffs = matrix(log_n, width, seed);
233 let shift = BinaryField128::from_le_byte_iter(
234 shift.to_le_bytes().into_iter().cycle(),
235 );
236
237 let lde = PolyBasisNtt::default().shifted_lde_batch(coeffs.clone(), added, shift);
238 let naive = NaiveAdditiveNtt::<BinaryField128>::default()
239 .shifted_lde_batch(coeffs.clone(), added, shift);
240 prop_assert_eq!(&lde, &naive);
241 prop_assert_eq!(&lde.values[..coeffs.values.len()], &coeffs.values[..]);
242 }
243 }
244
245 #[test]
250 #[should_panic]
251 fn shifted_ntt_batch_rejects_a_non_power_of_two_height() {
252 let coeffs = RowMajorMatrix::new(matrix(0, 1, 0).values.repeat(3), 1);
253 let _ = PolyBasisNtt::default().ntt_batch(coeffs);
254 }
255}