1use core::ops::{AddAssign, Mul};
2
3use p3_dft::TwoAdicSubgroupDft;
4use p3_field::{Algebra, PrimeCharacteristicRing, TwoAdicField};
5
6#[inline(always)]
8pub fn dot_product<T, const N: usize>(u: [T; N], v: [T; N]) -> T
9where
10 T: Copy + AddAssign + Mul<Output = T>,
11{
12 debug_assert_ne!(N, 0);
13 let mut dp = u[0] * v[0];
14 for i in 1..N {
15 dp += u[i] * v[i];
16 }
17 dp
18}
19
20pub fn apply_circulant<R: PrimeCharacteristicRing, const N: usize>(
28 circ_matrix: &[u64; N],
29 input: &[R; N],
30) -> [R; N] {
31 let matrix = circ_matrix.map(R::from_u64);
32
33 core::array::from_fn(|row| {
34 let rotated: [R; N] = core::array::from_fn(|col| matrix[(N + col - row) % N].clone());
36 R::dot_product(&rotated, input)
37 })
38}
39
40pub const fn first_row_to_first_col<const N: usize, T: Copy>(v: &[T; N]) -> [T; N] {
53 let mut output = *v;
55 let mut i = 1;
56 while i < N {
57 output[i] = v[N - i];
59 i += 1;
60 }
61 output
62}
63
64#[inline]
70pub fn apply_circulant_fft<F: TwoAdicField, const N: usize, FFT: TwoAdicSubgroupDft<F>>(
71 fft: &FFT,
72 column: [u64; N],
73 input: &[F; N],
74) -> [F; N] {
75 let column = column.map(F::from_u64).to_vec();
77 let matrix = fft.dft(column);
78 let freq_column: [F; N] = matrix.try_into().unwrap();
79
80 apply_circulant_fft_precomputed(fft, &freq_column, input)
81}
82
83#[inline]
91pub fn apply_circulant_fft_precomputed<
92 F: TwoAdicField,
93 const N: usize,
94 FFT: TwoAdicSubgroupDft<F>,
95>(
96 fft: &FFT,
97 freq_column: &[F; N],
98 input: &[F; N],
99) -> [F; N] {
100 let mut input = fft.dft(input.to_vec());
102
103 for (&coefficient, value) in freq_column.iter().zip(&mut input) {
105 *value = coefficient * *value;
106 }
107
108 let output = fft.idft(input);
110 output.try_into().unwrap()
111}
112
113#[inline]
131pub fn mds_multiply<F, A, const WIDTH: usize>(state: &mut [A; WIDTH], matrix: &[[F; WIDTH]; WIDTH])
132where
133 F: PrimeCharacteristicRing,
134 A: Algebra<F>,
135{
136 let input = state.clone();
138
139 for (out, row) in state.iter_mut().zip(matrix.iter()) {
141 *out = A::mixed_dot_product(&input, row);
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use p3_baby_bear::BabyBear;
148 use p3_dft::NaiveDft;
149 use p3_field::PrimeCharacteristicRing;
150 use proptest::prelude::*;
151
152 use super::*;
153
154 type F = BabyBear;
155
156 fn arb_f() -> impl Strategy<Value = F> {
157 prop::num::u32::ANY.prop_map(F::from_u32)
158 }
159
160 #[test]
161 fn first_row_to_first_col_even_length() {
162 let input = [0, 1, 2, 3, 4, 5];
163 assert_eq!(first_row_to_first_col(&input), [0, 5, 4, 3, 2, 1]);
164 }
165
166 #[test]
167 fn first_row_to_first_col_odd_length() {
168 let input = [10, 20, 30, 40, 50];
169 assert_eq!(first_row_to_first_col(&input), [10, 50, 40, 30, 20]);
170 }
171
172 #[test]
173 fn first_row_to_first_col_single_element() {
174 assert_eq!(first_row_to_first_col(&[42]), [42]);
175 }
176
177 #[test]
178 fn first_row_to_first_col_two_elements() {
179 assert_eq!(first_row_to_first_col(&[1, 2]), [1, 2]);
180 }
181
182 #[test]
183 fn apply_circulant_identity() {
184 let identity_row: [u64; 4] = [1, 0, 0, 0];
186 let input: [F; 4] = [5, 10, 15, 20].map(F::from_u32);
187 assert_eq!(apply_circulant(&identity_row, &input), input);
188 }
189
190 #[test]
191 fn apply_circulant_all_ones() {
192 let ones: [u64; 4] = [1, 1, 1, 1];
194 let input: [F; 4] = [1, 2, 3, 4].map(F::from_u32);
195 let sum = F::from_u32(10);
196 assert_eq!(apply_circulant(&ones, &input), [sum; 4]);
197 }
198
199 #[test]
200 fn apply_circulant_scalar() {
201 let row: [u64; 4] = [7, 0, 0, 0];
203 let input: [F; 4] = [1, 2, 3, 4].map(F::from_u32);
204 let expected: [F; 4] = [7, 14, 21, 28].map(F::from_u32);
205 assert_eq!(apply_circulant(&row, &input), expected);
206 }
207
208 #[test]
209 fn apply_circulant_size_1() {
210 let row: [u64; 1] = [5];
212 let input: [F; 1] = [F::from_u32(3)];
213 assert_eq!(apply_circulant(&row, &input), [F::from_u32(15)]);
214 }
215
216 #[test]
217 fn apply_circulant_fft_matches_naive_4() {
218 let row: [u64; 4] = [2, 3, 5, 7];
220 let col = first_row_to_first_col(&row);
221 let input: [F; 4] = [1, 2, 3, 4].map(F::from_u32);
222
223 let naive = apply_circulant(&row, &input);
224 let fft_result = apply_circulant_fft(&NaiveDft, col, &input);
225 assert_eq!(naive, fft_result);
226 }
227
228 #[test]
229 fn apply_circulant_fft_identity() {
230 let row: [u64; 4] = [1, 0, 0, 0];
232 let col = first_row_to_first_col(&row);
233 let input: [F; 4] = [5, 10, 15, 20].map(F::from_u32);
234 assert_eq!(apply_circulant_fft(&NaiveDft, col, &input), input);
235 }
236
237 proptest! {
238 #[test]
239 fn first_row_to_first_col_involution(v in prop::array::uniform4(0u64..1000)) {
240 let col = first_row_to_first_col(&v);
241 let back = first_row_to_first_col(&col);
242 prop_assert_eq!(back, v);
243 }
244
245 #[test]
246 fn apply_circulant_fft_matches_naive(
247 row in prop::array::uniform4(0u64..1000),
248 input in prop::array::uniform4(arb_f()),
249 ) {
250 let col = first_row_to_first_col(&row);
251 let naive = apply_circulant(&row, &input);
252 let fft_result = apply_circulant_fft(&NaiveDft, col, &input);
253 prop_assert_eq!(naive, fft_result);
254 }
255
256 #[test]
257 fn apply_circulant_linearity(
258 row in prop::array::uniform4(0u64..100),
259 a in prop::array::uniform4(arb_f()),
260 b in prop::array::uniform4(arb_f()),
261 ) {
262 let sum_input: [F; 4] = core::array::from_fn(|i| a[i] + b[i]);
263 let ca = apply_circulant(&row, &a);
264 let cb = apply_circulant(&row, &b);
265 let c_sum = apply_circulant(&row, &sum_input);
266 for i in 0..4 {
267 prop_assert_eq!(c_sum[i], ca[i] + cb[i]);
268 }
269 }
270
271 #[test]
272 fn apply_circulant_zero_matrix(input in prop::array::uniform4(arb_f())) {
273 let zeros: [u64; 4] = [0; 4];
274 let result = apply_circulant(&zeros, &input);
275 prop_assert_eq!(result, [F::ZERO; 4]);
276 }
277 }
278}