1use core::ops::Mul;
2use std::iter::zip;
3
4use getset::Getters;
5use itertools::Itertools;
6use p3_dft::{Radix2Bowers, TwoAdicSubgroupDft};
7use p3_field::{ExtensionField, Field, TwoAdicField};
8use p3_matrix::{dense::RowMajorMatrix, Matrix};
9use p3_maybe_rayon::prelude::*;
10use p3_util::{log2_ceil_usize, log2_strict_usize};
11use tracing::instrument;
12
13use crate::{
14 dft::Radix2BowersSerial,
15 poly_common::{eval_eq_uni, evals_eq_hypercube_serial, UnivariatePoly},
16 prover::{ColMajorMatrix, ColMajorMatrixView, MatrixDimensions},
17 utils::batch_multiplicative_inverse_serial,
18};
19
20#[derive(Getters)]
26pub struct Mle<F> {
27 #[getset(get = "pub")]
28 coeffs: Vec<F>,
29}
30
31impl<F: Field> Mle<F> {
32 pub fn from_coeffs(coeffs: Vec<F>) -> Self {
33 Self { coeffs }
34 }
35
36 pub fn from_evaluations(evals: &[F]) -> Self {
45 assert!(!evals.is_empty(), "Evaluations cannot be empty");
46 let mut coeffs = evals.to_vec();
47 Self::evals_to_coeffs_inplace(&mut coeffs);
48 Self { coeffs }
49 }
50
51 pub fn into_coeffs(self) -> Vec<F> {
52 self.coeffs
53 }
54
55 pub fn eval_at_point<F2: Field, EF: ExtensionField<F> + Mul<F2, Output = EF>>(
59 &self,
60 x: &[F2],
61 ) -> EF {
62 debug_assert_eq!(log2_strict_usize(self.coeffs.len()), x.len());
63 let mut res = EF::ZERO;
64 for (i, coeff) in self.coeffs.iter().enumerate() {
65 let mut term = EF::from(*coeff);
66 for (j, x_j) in x.iter().enumerate() {
67 if (i >> j) & 1 == 1 {
68 term = term * *x_j;
69 }
70 }
71 res += term;
72 }
73 res
74 }
75
76 pub fn eval_at_point_inplace<F2>(self, x: &[F2]) -> F
80 where
81 F2: Field,
82 F: ExtensionField<F2>,
83 {
84 let mut buf = self.coeffs;
85 debug_assert_eq!(buf.len(), 1 << x.len());
86 let mut len = 1usize << x.len();
87 for &xj in x.iter().rev() {
89 len >>= 1;
90 let (left, right) = buf.split_at_mut(len);
91 for (li, &ri) in zip(left.iter_mut(), right.iter()) {
92 *li += ri * xj;
93 }
94 }
95 buf[0]
96 }
97
98 pub fn evals_to_coeffs_inplace(a: &mut [F]) {
99 let n = log2_strict_usize(a.len());
100 for log_step in 0..n {
103 let step = 1usize << log_step;
104 let span = step << 1;
105 a.par_chunks_exact_mut(span).for_each(|chunk| {
106 let (first_half, second_half) = chunk.split_at_mut(step);
107 first_half
108 .par_iter()
109 .zip(second_half.par_iter_mut())
110 .for_each(|(u, v)| {
111 *v -= *u;
112 });
113 });
114 }
115 }
116
117 pub fn coeffs_to_evals_inplace(a: &mut [F]) {
118 let n = log2_strict_usize(a.len());
119
120 for b in 0..n {
121 let step = 1usize << b;
122 let span = step << 1;
123 for i in (0..a.len()).step_by(span) {
124 for j in 0..step {
125 let u = i + j;
126 let v = u + step;
127 a[v] += a[u];
128 }
129 }
130 }
131 }
132}
133
134pub fn evals_eq_hypercube<F: Field>(x: &[F]) -> Vec<F> {
142 let n = x.len();
143 let mut out = F::zero_vec(1 << n);
144 out[0] = F::ONE;
145 for (i, &x_i) in x.iter().enumerate() {
146 let (los, his) = out[..2 << i].split_at_mut(1 << i);
147 los.par_iter_mut()
148 .zip(his.par_iter_mut())
149 .for_each(|(lo, hi)| {
150 *hi = *lo * x_i;
151 *lo *= F::ONE - x_i;
152 })
153 }
154 out
155}
156
157pub fn evals_mobius_eq_hypercube<F: Field>(u_tilde: &[F]) -> Vec<F> {
166 let n = u_tilde.len();
167 let mut out = F::zero_vec(1 << n);
168 out[0] = F::ONE;
169 for (i, &u_i) in u_tilde.iter().enumerate() {
170 let w0 = F::ONE - u_i.double();
171 let w1 = u_i;
172 let (los, his) = out[..2 << i].split_at_mut(1 << i);
173 los.par_iter_mut()
174 .zip(his.par_iter_mut())
175 .for_each(|(lo, hi)| {
176 let prev = *lo;
177 *hi = prev * w1;
178 *lo = prev * w0;
179 })
180 }
181 out
182}
183
184pub fn evals_eq_hypercubes<'a, F: Field>(n: usize, x: impl IntoIterator<Item = &'a F>) -> Vec<F> {
187 let mut out = F::zero_vec((2 << n) - 1);
188 out[0] = F::ONE;
189 for (i, &x_i) in x.into_iter().enumerate() {
190 for y in 0..(1 << i) {
191 out[(1 << (i + 1)) - 1 + (2 * y + 1)] = out[(1 << i) - 1 + y] * x_i;
192 out[(1 << (i + 1)) - 1 + (2 * y)] = out[(1 << i) - 1 + y] * (F::ONE - x_i);
193 }
194 }
195 out
196}
197
198pub fn evals_eq_hyperprism<F: TwoAdicField, EF: ExtensionField<F>>(
201 omega_pows: &[F],
202 z: EF,
203 x: &[EF],
204) -> Vec<EF> {
205 let d_size = omega_pows.len();
207 let l_skip = log2_strict_usize(d_size);
208 let n = x.len();
209 let mut out = EF::zero_vec(d_size << n);
210 for (omega_pow, eq_uni) in zip(omega_pows, out.iter_mut()) {
211 *eq_uni = eval_eq_uni(l_skip, z, EF::from(*omega_pow));
212 }
213 for (i, &x_i) in x.iter().enumerate() {
214 for y in (0..d_size << i).rev() {
215 let eq_prev = out[y];
216 out[y | (d_size << i)] = eq_prev * x_i;
218 out[y] = eq_prev * (EF::ONE - x_i);
219 }
220 }
221 out
222}
223
224pub fn eq_sharp_uni_poly<EF: TwoAdicField>(xi_1: &[EF]) -> UnivariatePoly<EF> {
225 let evals = evals_eq_hypercube_serial(xi_1);
226 UnivariatePoly::from_evals_idft(&evals)
227}
228
229#[derive(Getters)]
236pub struct Ple<F> {
237 #[getset(get = "pub")]
238 pub(crate) coeffs: Vec<F>,
239}
240
241impl<F: TwoAdicField> Ple<F> {
242 pub fn from_evaluations(l_skip: usize, evals: &[F]) -> Self {
253 let prism_dim = log2_strict_usize(evals.len());
254 assert!(
255 prism_dim >= l_skip,
256 "Total variables must be at least l_skip"
257 );
258 let mut buf: Vec<_> = evals
261 .par_chunks_exact(1 << l_skip)
262 .flat_map(|chunk| {
263 let dft = Radix2Bowers;
264 dft.idft(chunk.to_vec())
265 })
266 .collect();
267
268 let n = prism_dim - l_skip;
269 for i in 0..n {
272 let step = 1usize << (l_skip + i);
273 let span = step << 1;
274 buf.par_chunks_exact_mut(span).for_each(|chunk| {
275 let (first_half, second_half) = chunk.split_at_mut(step);
276 first_half
277 .par_iter()
278 .zip(second_half.par_iter_mut())
279 .for_each(|(u, v)| {
280 *v -= *u;
281 });
282 });
283 }
284 Self { coeffs: buf }
285 }
286
287 pub fn eval_at_point<EF: ExtensionField<F>>(&self, l_skip: usize, z: EF, x: &[EF]) -> EF {
288 let n = x.len();
289 debug_assert_eq!(l_skip + n, log2_strict_usize(self.coeffs.len()));
290 let mut res = EF::ZERO;
291 let mut z_pow = EF::ONE;
292 for (i, coeff) in self.coeffs.iter().enumerate() {
293 if i.trailing_zeros() >= l_skip as u32 {
294 z_pow = EF::ONE;
295 }
296 let i_x = i >> l_skip;
297 let mut term = z_pow * *coeff;
298 for (j, x_j) in x.iter().enumerate() {
299 if (i_x >> j) & 1 == 1 {
300 term *= *x_j;
301 }
302 }
303 z_pow *= z;
304 res += term;
305 }
306 res
307 }
308
309 pub fn into_coeffs(self) -> Vec<F> {
310 self.coeffs
311 }
312}
313
314pub fn eval_to_coeff_rs_message<F: TwoAdicField>(l_skip: usize, evals: &[F]) -> Vec<F> {
326 assert!(!evals.is_empty(), "Evaluations cannot be empty");
327 let prism_dim = log2_strict_usize(evals.len());
328 assert!(
329 prism_dim >= l_skip,
330 "Total variables must be at least l_skip"
331 );
332
333 let chunk_len = 1usize << l_skip;
334 let mut buf: Vec<_> = evals
335 .par_chunks_exact(chunk_len)
336 .flat_map(|chunk| {
337 let dft = Radix2Bowers;
338 dft.idft(chunk.to_vec())
339 })
340 .collect();
341
342 buf.par_chunks_exact_mut(chunk_len)
345 .for_each(Mle::coeffs_to_evals_inplace);
346
347 buf
348}
349
350pub struct MleMatrix<F> {
351 pub columns: Vec<Mle<F>>,
352}
353
354impl<F: Field> MleMatrix<F> {
355 pub fn from_evaluations(evals: &ColMajorMatrix<F>) -> Self {
356 let width = evals.width();
357 let columns = (0..width)
358 .into_par_iter()
359 .map(|j| Mle::from_evaluations(evals.column(j)))
360 .collect();
361 Self { columns }
362 }
363}
364
365pub struct PleMatrix<F> {
366 pub columns: Vec<Ple<F>>,
367}
368
369impl<F: TwoAdicField> PleMatrix<F> {
370 pub fn from_evaluations(l_skip: usize, evals: &ColMajorMatrixView<F>) -> Self {
371 let width = evals.width();
372 let columns = (0..width)
373 .into_par_iter()
374 .map(|j| Ple::from_evaluations(l_skip, evals.column(j)))
375 .collect();
376 Self { columns }
377 }
378}
379
380impl<F: Field> UnivariatePoly<F> {
381 #[instrument(level = "debug", skip_all)]
382 pub fn lagrange_interpolate<BF: Field>(points: &[BF], evals: &[F]) -> Self
383 where
384 F: ExtensionField<BF>,
385 {
386 assert_eq!(points.len(), evals.len());
387 let len = points.len();
388
389 if len == 0 {
391 return Self(vec![]);
392 }
393 if len == 1 {
394 return Self(vec![evals[0]]);
395 }
396
397 let mut denominators = Vec::with_capacity(len * (len - 1));
403 for i in 0..len {
404 for j in 0..len {
405 if i != j {
406 denominators.push(points[i] - points[j]);
407 }
408 }
409 }
410
411 let inv_denominators = batch_multiplicative_inverse_serial(&denominators);
413
414 let mut coeffs = vec![F::ZERO; len];
416
417 let mut lagrange_poly = Vec::with_capacity(len);
419
420 #[allow(clippy::needless_range_loop)]
421 for i in 0..len {
422 if evals[i] == F::ZERO {
424 continue;
425 }
426
427 lagrange_poly.clear();
432 lagrange_poly.push(F::ONE);
433
434 let inv_denom_start = i * (len - 1);
436 let mut inv_idx = 0;
437
438 #[allow(clippy::needless_range_loop)]
440 for j in 0..len {
441 if i != j {
442 let scale = inv_denominators[inv_denom_start + inv_idx];
443 inv_idx += 1;
444
445 lagrange_poly.push(F::ZERO); for k in (1..lagrange_poly.len()).rev() {
451 let prev_coeff = lagrange_poly[k - 1] * scale;
452 lagrange_poly[k] += prev_coeff;
453 lagrange_poly[k - 1] = -prev_coeff * points[j];
454 }
455 }
456 }
457
458 for (k, &coeff) in lagrange_poly.iter().enumerate() {
460 coeffs[k] += evals[i] * coeff;
461 }
462 }
463
464 Self(coeffs)
465 }
466}
467
468impl<F: TwoAdicField> UnivariatePoly<F> {
469 fn chirp_z(poly: &[F], omega: F, n: usize) -> Vec<F> {
471 if n == 0 {
472 return Vec::new();
473 }
474 if poly.is_empty() {
475 return vec![F::ZERO; n];
476 }
477 let s = poly.len() + n;
478 let omega_powers = (0..(s as u64))
479 .map(|i| omega.exp_u64(i * (i.saturating_sub(1)) / 2))
480 .collect_vec();
481 let omega_powers_inv = batch_multiplicative_inverse_serial(&omega_powers);
482 let mut p = zip(poly, &omega_powers_inv)
483 .map(|(&c, &inv)| c * inv)
484 .collect_vec();
485 let mut q = omega_powers.iter().rev().copied().collect_vec();
486
487 let dft_deg = (p.len() + q.len() - 1).next_power_of_two();
488 p.resize(dft_deg, F::ZERO);
489 q.resize(dft_deg, F::ZERO);
490 let dft = Radix2BowersSerial;
491 p = dft.dft(p);
492 q = dft.dft(q);
493 for (x, y) in p.iter_mut().zip(q.iter()) {
494 *x *= *y;
495 }
496 p = dft.idft(p);
497 zip(p.into_iter().skip(n).take(n).rev(), omega_powers_inv)
498 .map(|(x, inv)| x * inv)
499 .collect()
500 }
501
502 fn geometric_sequence_linear_product_helper(
506 dft: &Radix2BowersSerial,
507 z: F,
508 n: usize,
509 ) -> Vec<F> {
510 if n == 1 {
511 vec![F::ONE, F::NEG_ONE]
512 } else if n % 2 == 1 {
513 let mut prev = Self::geometric_sequence_linear_product_helper(dft, z, n - 1);
514 let zp = z.exp_u64((n - 1) as u64);
515 prev.push(F::ZERO);
516 for i in (1..prev.len()).rev() {
517 let value = prev[i - 1] * zp;
518 prev[i] -= value;
519 }
520 prev
521 } else {
522 let mut prev = Self::geometric_sequence_linear_product_helper(dft, z, n / 2);
523 let zp = z.exp_u64((n / 2) as u64);
524 let mut another = prev
525 .iter()
526 .zip(zp.powers())
527 .map(|(a, b)| *a * b)
528 .collect_vec();
529 let len = prev.len().next_power_of_two() * 2;
530 prev.resize(len, F::ZERO);
531 another.resize(len, F::ZERO);
532 prev = dft.dft(prev);
533 another = dft.dft(another);
534 for (x, y) in prev.iter_mut().zip(another.into_iter()) {
535 *x *= y;
536 }
537 prev = dft.idft(prev);
538 prev.truncate(n + 1);
539 prev
540 }
541 }
542
543 #[instrument(level = "debug", skip_all)]
547 pub fn from_evals(evals: &[F]) -> Self {
548 let n = evals.len();
549 let log_n = log2_ceil_usize(n);
550 let omega = F::two_adic_generator(log_n);
551 let omega_pows = omega.powers().take((1 << log_n) + 1).collect_vec();
552 if n == 0 {
553 return Self(Vec::new());
554 }
555 if n == 1 {
556 return Self(vec![evals[0]]);
557 }
558
559 let mut positive_denoms = vec![F::ONE; n];
565 let mut negative_denoms = vec![F::ONE; n];
566 for i in 0..(n - 1) {
567 positive_denoms[i + 1] = positive_denoms[i] / (F::ONE - omega_pows[i + 1]);
568 negative_denoms[i + 1] =
569 negative_denoms[i] / (F::ONE - omega_pows[(1 << log_n) - 1 - i]);
570 }
571 let omega_inv = omega_pows[(1 << log_n) - 1];
572 let y = (0..n)
573 .map(|i| {
574 evals[i]
575 * omega_inv.exp_u64(((n - 1) * i) as u64)
576 * negative_denoms[i]
577 * positive_denoms[n - 1 - i]
578 })
579 .collect_vec();
580
581 let mut rhs = Self::chirp_z(&y, omega, n);
590
591 let dft = Radix2BowersSerial;
593 let mut denom = Self::geometric_sequence_linear_product_helper(&dft, omega, n);
594
595 let len = (denom.len() + rhs.len() - 1).next_power_of_two();
596 denom.resize(len, F::ZERO);
597 rhs.resize(len, F::ZERO);
598 denom = dft.dft(denom);
599 rhs = dft.dft(rhs);
600 let res = denom.into_iter().zip(rhs).map(|(a, b)| a * b).collect_vec();
601 let mut res = dft.idft(res);
602 res.truncate(n);
603 res.reverse();
605 Self(res)
606 }
607
608 pub fn from_evals_idft(evals: &[F]) -> Self {
613 let dft = Radix2BowersSerial;
615 let coeffs = dft.idft(evals.to_vec());
616 Self(coeffs)
617 }
618
619 pub fn from_geometric_cosets_evals_idft<BF: Field>(
622 evals: RowMajorMatrix<F>,
623 shift: BF,
624 init: BF,
625 ) -> Self
626 where
627 F: ExtensionField<BF>,
628 {
629 let height = Matrix::height(&evals);
630 let width = Matrix::width(&evals);
631 if height == 0 || width == 0 {
632 return Self(Vec::new());
633 }
634
635 let log_height = log2_strict_usize(height);
636 let dft = Radix2BowersSerial;
637
638 let mut coeffs_mat = dft.idft_batch(evals);
641 let shift_inv = shift.inverse();
642 let init_inv = init.inverse();
643 let shift_invs = (0..width)
645 .fold(
646 (Vec::with_capacity(width), init_inv),
647 |(mut acc, pow), _| {
648 acc.push(pow);
649 (acc, pow * shift_inv)
650 },
651 )
652 .0;
653 let mut shift_pows = vec![F::ONE; width];
654 for row in coeffs_mat.rows_mut() {
655 for (col, value) in row.iter_mut().enumerate() {
656 *value *= shift_pows[col];
657 shift_pows[col] *= shift_invs[col];
658 }
659 }
660
661 let coset_base = shift.exp_power_of_2(log_height);
665 let init_base = init.exp_power_of_2(log_height);
666 let lagrange_basis = lagrange_basis_from_geometric_points(coset_base, width, init_base);
667 let mut coeffs = vec![F::ZERO; height * width];
668 for (row_idx, row_vals) in coeffs_mat.row_slices().enumerate() {
669 let mut poly_coeffs = vec![F::ZERO; width];
670 for (i, &value) in row_vals.iter().enumerate() {
671 if value == F::ZERO {
672 continue;
673 }
674 for (k, basis_coeff) in lagrange_basis[i].iter().enumerate() {
675 poly_coeffs[k] += value * *basis_coeff;
676 }
677 }
678 for (coset_idx, coeff) in poly_coeffs.into_iter().enumerate() {
679 coeffs[coset_idx * height + row_idx] = coeff;
680 }
681 }
682 Self(coeffs)
683 }
684}
685
686fn lagrange_basis_from_geometric_points<F: Field>(base: F, width: usize, init: F) -> Vec<Vec<F>> {
688 if width == 0 {
689 return Vec::new();
690 }
691 if width == 1 {
692 return vec![vec![F::ONE]];
693 }
694
695 let points = (0..width)
697 .fold((Vec::with_capacity(width), init), |(mut acc, pow), _| {
698 acc.push(pow);
699 (acc, pow * base)
700 })
701 .0;
702
703 let mut root_poly = vec![F::ONE];
705 for &x in &points {
706 root_poly.push(F::ZERO);
707 for k in (1..root_poly.len()).rev() {
708 let prev = root_poly[k - 1];
709 root_poly[k] = prev - x * root_poly[k];
710 }
711 root_poly[0] = -x * root_poly[0];
712 }
713
714 let mut prefix = vec![F::ONE; width];
716 for (i, base_pow) in base.powers().skip(1).take(width - 1).enumerate() {
717 prefix[i + 1] = prefix[i] * (F::ONE - base_pow);
718 }
719
720 let mut quotients = Vec::with_capacity(width);
722 for (i, &x) in points.iter().enumerate() {
723 let mut q = vec![F::ZERO; width];
724 q[width - 1] = root_poly[width];
725 for k in (0..width - 1).rev() {
726 q[k] = root_poly[k + 1] + x * q[k + 1];
727 }
728
729 let sign = if i % 2 == 0 { F::ONE } else { F::NEG_ONE };
737 let exp = i * (width - 1) - (i * (i + 1) / 2);
738 let pow = base.exp_u64(exp as u64);
739 let init_pow = init.exp_u64((width - 1) as u64);
740 let denom = sign * init_pow * pow * prefix[i] * prefix[width - 1 - i];
741 let inv_denom = denom.inverse();
742 for coeff in q.iter_mut() {
743 *coeff *= inv_denom;
744 }
745 quotients.push(q);
746 }
747 quotients
748}
749
750#[cfg(test)]
751mod tests {
752 use std::iter::zip;
753
754 use itertools::Itertools;
755 use openvm_stark_sdk::config::baby_bear_poseidon2::*;
756 use p3_field::{PrimeCharacteristicRing, TwoAdicField};
757 use p3_util::{log2_ceil_usize, log2_strict_usize};
758 use rand::{rngs::StdRng, Rng, SeedableRng};
759
760 use super::*;
761 use crate::poly_common::horner_eval;
762
763 #[test]
764 fn test_evals_mobius_eq_hypercube_matches_naive() {
765 let mut rng = StdRng::seed_from_u64(0);
766
767 for n in 0..=10usize {
768 let u_tilde = (0..n)
769 .map(|_| F::from_u64(rng.random()))
770 .collect::<Vec<_>>();
771 let evals = evals_mobius_eq_hypercube(&u_tilde);
772 assert_eq!(evals.len(), 1 << n);
773
774 for (mask, &eval) in evals.iter().enumerate() {
775 let mut expected = F::ONE;
776 for (i, &w) in u_tilde.iter().enumerate() {
777 expected *= if (mask >> i) & 1 == 0 {
778 F::ONE - w.double()
779 } else {
780 w
781 };
782 }
783 assert_eq!(eval, expected);
784 }
785 }
786 }
787
788 #[test]
789 fn test_decoder_kernel_identity() {
790 let mut rng = StdRng::seed_from_u64(1);
791
792 for m in 0..=10usize {
793 let a = (0..(1usize << m))
794 .map(|_| F::from_u64(rng.random()))
795 .collect::<Vec<_>>();
796
797 let mut rs_coeffs = a.clone();
799 Mle::coeffs_to_evals_inplace(&mut rs_coeffs);
800
801 let mut hatf_evals = rs_coeffs.clone();
804 Mle::coeffs_to_evals_inplace(&mut hatf_evals);
805
806 let u_tilde = (0..m)
807 .map(|_| F::from_u64(rng.random()))
808 .collect::<Vec<_>>();
809
810 let mobius_eq_evals = evals_mobius_eq_hypercube(&u_tilde);
811 let lhs = zip(&hatf_evals, &mobius_eq_evals).fold(F::ZERO, |acc, (&f, &g)| acc + f * g);
812
813 let rhs = a.iter().enumerate().fold(F::ZERO, |acc, (mask, &coeff)| {
815 let mut term = coeff;
816 for (i, &w) in u_tilde.iter().enumerate() {
817 if (mask >> i) & 1 == 1 {
818 term *= w;
819 }
820 }
821 acc + term
822 });
823
824 assert_eq!(lhs, rhs);
825 }
826 }
827
828 #[test]
829 fn test_lagrange_interpolation_round_trip() {
830 let mut rng = StdRng::seed_from_u64(0);
831 for degree in 0..8usize {
833 let num_evals: usize = degree + 1;
834
835 let mut original_coeffs = vec![];
837 for _ in 0..num_evals {
838 original_coeffs.push(F::from_u32(rng.random()));
840 }
841
842 let log_domain_size = log2_ceil_usize(num_evals);
844 let omega = F::two_adic_generator(log_domain_size);
845
846 let mut evals = vec![];
848 let points = omega.powers().take(num_evals).collect_vec();
849 for &x in &points {
850 evals.push(horner_eval(&original_coeffs, x));
851 }
852
853 let reconstructed_poly = UnivariatePoly::lagrange_interpolate(&points, &evals);
855
856 for (i, (coeff, reconstructed_coeff)) in
858 zip(original_coeffs, reconstructed_poly.0).enumerate()
859 {
860 assert_eq!(
861 coeff, reconstructed_coeff,
862 "Coefficient mismatch at index {} for degree {} polynomial",
863 i, degree
864 );
865 }
866 }
867 }
868
869 #[test]
870 fn test_chirp_z() {
871 let mut rng = StdRng::seed_from_u64(0);
872 for degree in 0..8usize {
873 let num_evals: usize = degree + 1;
874
875 let mut original_coeffs = vec![];
877 for _ in 0..num_evals {
878 original_coeffs.push(F::from_u32(rng.random()));
880 }
881
882 let log_domain_size = log2_ceil_usize(num_evals);
883 let omega = F::two_adic_generator(log_domain_size);
884
885 let mut evals = vec![];
887 let points = omega.powers().take(num_evals).collect_vec();
888 for &x in &points {
889 evals.push(horner_eval(&original_coeffs, x));
890 }
891
892 assert_eq!(
893 evals,
894 UnivariatePoly::chirp_z(&original_coeffs, omega, num_evals)
895 );
896
897 let reconstructed_poly = UnivariatePoly::from_evals(&evals);
898
899 for (i, (coeff, reconstructed_coeff)) in
901 zip(original_coeffs, reconstructed_poly.0).enumerate()
902 {
903 assert_eq!(
904 coeff, reconstructed_coeff,
905 "Coefficient mismatch at index {} for degree {} polynomial",
906 i, degree
907 );
908 }
909 }
910 }
911
912 #[test]
913 fn test_from_geometric_cosets_evals_idft_round_trip() {
914 let mut rng = StdRng::seed_from_u64(0);
915 let height = 8usize;
916 let log_height = log2_strict_usize(height);
917 let omega = F::two_adic_generator(log_height);
918
919 let configs = [
920 (F::GENERATOR, F::ONE),
921 (F::two_adic_generator(log_height + 2), F::GENERATOR),
922 ];
923
924 for (shift, init) in configs {
925 for width in 2..=4usize {
926 let coeffs = (0..height * width)
927 .map(|_| F::from_u32(rng.random()))
928 .collect_vec();
929 let coeffs_ref = &coeffs;
930
931 let evals: Vec<F> = (0..height)
933 .flat_map(|row| {
934 let omega_pow = omega.exp_u64(row as u64);
935 (0..width).map(move |col| {
936 let coset_shift = init * shift.exp_u64(col as u64);
937 horner_eval(coeffs_ref, coset_shift * omega_pow)
938 })
939 })
940 .collect_vec();
941
942 let evals_mat = RowMajorMatrix::new(evals, width);
943 let poly = UnivariatePoly::from_geometric_cosets_evals_idft(evals_mat, shift, init);
944 assert_eq!(
945 poly.into_coeffs(),
946 coeffs,
947 "width {width} round-trip failed"
948 );
949 }
950 }
951 }
952}