1use core::marker::PhantomData;
4
5use p3_binary_field::TowerLevel;
6use p3_matrix::Matrix;
7use p3_matrix::dense::RowMajorMatrix;
8use p3_util::log2_strict_usize;
9
10use crate::domain::domain_point;
11use crate::traits::AdditiveNtt;
12
13#[derive(Clone, Debug, Default)]
20pub struct NaiveAdditiveNtt<F> {
21 _marker: PhantomData<F>,
22}
23
24fn normalised_subspace_poly<F: TowerLevel>(j: usize, x: F) -> F {
26 let vanishing = |y: F| (0..1 << j).map(|m| y + domain_point::<F>(m)).product::<F>();
27 vanishing(x)
28 * vanishing(F::cantor_basis(j))
29 .try_inverse()
30 .expect("W_j does not vanish at v_j, which lies outside S_j")
31}
32
33impl<F: TowerLevel> AdditiveNtt<F> for NaiveAdditiveNtt<F> {
34 fn shifted_ntt_batch(&self, mat: RowMajorMatrix<F>, shift: F) -> RowMajorMatrix<F> {
35 let width = mat.width();
36 let height = mat.height();
37 let log_n = log2_strict_usize(height);
38
39 let mut out = F::zero_vec(width * height);
40 for row in 0..height {
41 let x = shift + domain_point::<F>(row);
42 let mut basis = F::zero_vec(height);
44 basis[0] = F::ONE;
45 for j in 0..log_n {
46 let w = normalised_subspace_poly::<F>(j, x);
47 for i in 0..1 << j {
48 basis[i + (1 << j)] = basis[i] * w;
49 }
50 }
51 for col in 0..width {
52 out[row * width + col] = (0..height)
53 .map(|i| basis[i] * mat.values[i * width + col])
54 .sum();
55 }
56 }
57 RowMajorMatrix::new(out, width)
58 }
59
60 fn shifted_intt_batch(&self, mut mat: RowMajorMatrix<F>, shift: F) -> RowMajorMatrix<F> {
61 let width = mat.width();
62 let log_n = log2_strict_usize(mat.height());
63
64 for j in 0..log_n {
66 let half = 1 << j;
67 for (block, base) in (0..mat.height()).step_by(half << 1).enumerate() {
68 let t =
69 normalised_subspace_poly::<F>(j, shift + domain_point::<F>(block << (j + 1)));
70 for k in 0..half {
71 for col in 0..width {
72 let u = mat.values[(base + k) * width + col];
73 let v = mat.values[(base + k + half) * width + col];
74 let hi = u + v;
75 mat.values[(base + k) * width + col] = u + t * hi;
76 mat.values[(base + k + half) * width + col] = hi;
77 }
78 }
79 }
80 }
81 mat
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use p3_binary_field::{BinaryField8, BinaryField16, TowerLevel};
88 use p3_field::PrimeCharacteristicRing;
89 use p3_matrix::Matrix;
90 use p3_matrix::dense::RowMajorMatrix;
91 use rand::SeedableRng;
92 use rand::rngs::SmallRng;
93
94 use super::NaiveAdditiveNtt;
95 use crate::traits::AdditiveNtt;
96 use crate::{domain_point, subspace_polynomial};
97
98 #[test]
100 fn unit_vectors_evaluate_the_novel_basis() {
101 const LOG_N: usize = 3;
102 let ntt = NaiveAdditiveNtt::<BinaryField8>::default();
103
104 for k in 0..1 << LOG_N {
105 let mut coeffs = BinaryField8::zero_vec(1 << LOG_N);
106 coeffs[k] = BinaryField8::ONE;
107 let evals = ntt.ntt_batch(RowMajorMatrix::new_col(coeffs));
108
109 for m in 0..1 << LOG_N {
110 let x = domain_point::<BinaryField8>(m);
112 let expected = (0..LOG_N)
113 .filter(|j| k >> j & 1 == 1)
114 .map(|j| subspace_polynomial::<BinaryField8>(j, x))
115 .product::<BinaryField8>();
116 assert_eq!(evals.values[m], expected, "k={k} m={m}");
117 }
118 }
119 }
120
121 #[test]
123 fn naive_round_trips() {
124 let mut rng = SmallRng::seed_from_u64(1);
125 let ntt = NaiveAdditiveNtt::<BinaryField16>::default();
126 let coeffs = RowMajorMatrix::<BinaryField16>::rand(&mut rng, 1 << 6, 3);
127 let shift = BinaryField16::from_repr(0x1234);
128
129 let evals = ntt.shifted_ntt_batch(coeffs.clone(), shift);
130 assert_eq!(ntt.shifted_intt_batch(evals, shift), coeffs);
131 }
132
133 #[test]
135 fn lde_extends_the_input() {
136 let mut rng = SmallRng::seed_from_u64(2);
137 let ntt = NaiveAdditiveNtt::<BinaryField16>::default();
138 let evals = RowMajorMatrix::<BinaryField16>::rand(&mut rng, 1 << 5, 2);
139
140 let extended = ntt.lde_batch(evals.clone(), 2);
141 assert_eq!(extended.height(), evals.height() * 4);
142 assert_eq!(&extended.values[..evals.values.len()], &evals.values[..]);
143 }
144
145 #[test]
149 fn the_normalisation_divisor_is_one() {
150 for j in 0..8 {
151 let divisor = (0..1usize << j)
152 .map(|m| BinaryField8::cantor_basis(j) + domain_point::<BinaryField8>(m))
153 .product::<BinaryField8>();
154 assert_eq!(divisor, BinaryField8::ONE, "j={j}");
155 }
156 }
157}