1use alloc::vec::Vec;
4use core::{
5 default::Default,
6 fmt::Debug,
7 ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
8};
9
10use num::{One, Zero};
11
12use super::{Inverse, MODULUS, N, field::FalconFelt};
13use crate::{Felt, utils::zeroize::Zeroize};
14
15#[derive(Debug, Clone, Default)]
17pub struct Polynomial<F> {
18 pub coefficients: Vec<F>,
20}
21
22impl<F> Polynomial<F>
23where
24 F: Clone,
25{
26 pub fn new(coefficients: Vec<F>) -> Self {
28 Self { coefficients }
29 }
30}
31
32impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone + Inverse>
33 Polynomial<F>
34{
35 pub fn hadamard_mul(&self, other: &Self) -> Self {
37 Polynomial::new(
38 self.coefficients
39 .iter()
40 .zip(other.coefficients.iter())
41 .map(|(a, b)| *a * *b)
42 .collect(),
43 )
44 }
45 pub fn hadamard_div(&self, other: &Self) -> Self {
47 let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients);
48 Polynomial::new(
49 self.coefficients
50 .iter()
51 .zip(other_coefficients_inverse.iter())
52 .map(|(a, b)| *a * *b)
53 .collect(),
54 )
55 }
56
57 pub fn hadamard_inv(&self) -> Self {
59 let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients);
60 Polynomial::new(coefficients_inverse)
61 }
62}
63
64impl<F: Zero + PartialEq + Clone> Polynomial<F> {
65 pub fn degree(&self) -> Option<usize> {
67 if self.coefficients.is_empty() {
68 return None;
69 }
70 let mut max_index = self.coefficients.len() - 1;
71 while self.coefficients[max_index] == F::zero() {
72 max_index = max_index.checked_sub(1)?;
73 }
74 Some(max_index)
75 }
76
77 pub fn lc(&self) -> F {
79 match self.degree() {
80 Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(),
81 None => F::zero(),
82 }
83 }
84}
85
86impl<
89 F: One
90 + Zero
91 + Clone
92 + Neg<Output = F>
93 + MulAssign
94 + AddAssign
95 + Div<Output = F>
96 + Sub<Output = F>
97 + PartialEq,
98> Polynomial<F>
99{
100 pub fn reduce_by_cyclotomic(&self, n: usize) -> Self {
102 let mut coefficients = vec![F::zero(); n];
103 let mut sign = -F::one();
104 for (i, c) in self.coefficients.iter().cloned().enumerate() {
105 if i.is_multiple_of(n) {
106 sign *= -F::one();
107 }
108 coefficients[i % n] += sign.clone() * c;
109 }
110 Polynomial::new(coefficients)
111 }
112
113 pub fn field_norm(&self) -> Self {
120 let n = self.coefficients.len();
121 let mut f0_coefficients = vec![F::zero(); n / 2];
122 let mut f1_coefficients = vec![F::zero(); n / 2];
123 for i in 0..n / 2 {
124 f0_coefficients[i] = self.coefficients[2 * i].clone();
125 f1_coefficients[i] = self.coefficients[2 * i + 1].clone();
126 }
127 let f0 = Polynomial::new(f0_coefficients);
128 let f1 = Polynomial::new(f1_coefficients);
129 let f0_squared = (f0.clone() * f0).reduce_by_cyclotomic(n / 2);
130 let f1_squared = (f1.clone() * f1).reduce_by_cyclotomic(n / 2);
131 let x = Polynomial::new(vec![F::zero(), F::one()]);
132 f0_squared - (x * f1_squared).reduce_by_cyclotomic(n / 2)
133 }
134
135 pub fn lift_next_cyclotomic(&self) -> Self {
137 let n = self.coefficients.len();
138 let mut coefficients = vec![F::zero(); n * 2];
139 for i in 0..n {
140 coefficients[2 * i] = self.coefficients[i].clone();
141 }
142 Self::new(coefficients)
143 }
144
145 pub fn galois_adjoint(&self) -> Self {
151 Self::new(
152 self.coefficients
153 .iter()
154 .enumerate()
155 .map(|(i, c)| {
156 if i.is_multiple_of(2) {
157 c.clone()
158 } else {
159 c.clone().neg()
160 }
161 })
162 .collect(),
163 )
164 }
165}
166
167impl<F: Clone + Into<f64>> Polynomial<F> {
168 pub(crate) fn l2_norm_squared(&self) -> f64 {
169 self.coefficients
170 .iter()
171 .map(|i| Into::<f64>::into(i.clone()))
172 .map(|i| i * i)
173 .sum::<f64>()
174 }
175}
176
177impl<F> PartialEq for Polynomial<F>
178where
179 F: Zero + PartialEq + Clone + AddAssign,
180{
181 fn eq(&self, other: &Self) -> bool {
182 if self.is_zero() && other.is_zero() {
183 true
184 } else if self.is_zero() || other.is_zero() {
185 false
186 } else {
187 let self_degree = self.degree().unwrap();
188 let other_degree = other.degree().unwrap();
189 self.coefficients[0..=self_degree] == other.coefficients[0..=other_degree]
190 }
191 }
192}
193
194impl<F> Eq for Polynomial<F> where F: Zero + PartialEq + Clone + AddAssign {}
195
196impl<F> Add for &Polynomial<F>
197where
198 F: Add<Output = F> + AddAssign + Clone,
199{
200 type Output = Polynomial<F>;
201
202 fn add(self, rhs: Self) -> Self::Output {
203 let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
204 let mut coefficients = self.coefficients.clone();
205 for (i, c) in rhs.coefficients.iter().enumerate() {
206 coefficients[i] += c.clone();
207 }
208 coefficients
209 } else {
210 let mut coefficients = rhs.coefficients.clone();
211 for (i, c) in self.coefficients.iter().enumerate() {
212 coefficients[i] += c.clone();
213 }
214 coefficients
215 };
216 Self::Output { coefficients }
217 }
218}
219
220impl<F> Add for Polynomial<F>
221where
222 F: Add<Output = F> + AddAssign + Clone,
223{
224 type Output = Polynomial<F>;
225 fn add(self, rhs: Self) -> Self::Output {
226 let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
227 let mut coefficients = self.coefficients;
228 for (i, c) in rhs.coefficients.into_iter().enumerate() {
229 coefficients[i] += c;
230 }
231 coefficients
232 } else {
233 let mut coefficients = rhs.coefficients;
234 for (i, c) in self.coefficients.into_iter().enumerate() {
235 coefficients[i] += c;
236 }
237 coefficients
238 };
239 Self::Output { coefficients }
240 }
241}
242
243impl<F> AddAssign for Polynomial<F>
244where
245 F: Add<Output = F> + AddAssign + Clone,
246{
247 fn add_assign(&mut self, rhs: Self) {
248 if self.coefficients.len() >= rhs.coefficients.len() {
249 for (i, c) in rhs.coefficients.into_iter().enumerate() {
250 self.coefficients[i] += c;
251 }
252 } else {
253 let mut coefficients = rhs.coefficients;
254 for (i, c) in self.coefficients.iter().enumerate() {
255 coefficients[i] += c.clone();
256 }
257 self.coefficients = coefficients;
258 }
259 }
260}
261
262impl<F> Sub for &Polynomial<F>
263where
264 F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
265{
266 type Output = Polynomial<F>;
267
268 fn sub(self, rhs: Self) -> Self::Output {
269 self + &(-rhs)
270 }
271}
272
273impl<F> Sub for Polynomial<F>
274where
275 F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
276{
277 type Output = Polynomial<F>;
278
279 fn sub(self, rhs: Self) -> Self::Output {
280 self + (-rhs)
281 }
282}
283
284impl<F> SubAssign for Polynomial<F>
285where
286 F: Add<Output = F> + Neg<Output = F> + AddAssign + Clone + Sub<Output = F>,
287{
288 fn sub_assign(&mut self, rhs: Self) {
289 self.coefficients = self.clone().sub(rhs).coefficients;
290 }
291}
292
293impl<F: Neg<Output = F> + Clone> Neg for &Polynomial<F> {
294 type Output = Polynomial<F>;
295
296 fn neg(self) -> Self::Output {
297 Self::Output {
298 coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
299 }
300 }
301}
302
303impl<F: Neg<Output = F> + Clone> Neg for Polynomial<F> {
304 type Output = Self;
305
306 fn neg(self) -> Self::Output {
307 Self::Output {
308 coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
309 }
310 }
311}
312
313impl<F> Mul for &Polynomial<F>
314where
315 F: Add + AddAssign + Mul<Output = F> + Sub<Output = F> + Zero + PartialEq + Clone,
316{
317 type Output = Polynomial<F>;
318
319 fn mul(self, other: Self) -> Self::Output {
320 if self.is_zero() || other.is_zero() {
321 return Polynomial::<F>::zero();
322 }
323 let mut coefficients =
324 vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
325 for i in 0..self.coefficients.len() {
326 for j in 0..other.coefficients.len() {
327 coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
328 }
329 }
330 Polynomial { coefficients }
331 }
332}
333
334impl<F> Mul for Polynomial<F>
335where
336 F: Add + AddAssign + Mul<Output = F> + Zero + PartialEq + Clone,
337{
338 type Output = Self;
339
340 fn mul(self, other: Self) -> Self::Output {
341 if self.is_zero() || other.is_zero() {
342 return Self::zero();
343 }
344 let mut coefficients =
345 vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
346 for i in 0..self.coefficients.len() {
347 for j in 0..other.coefficients.len() {
348 coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
349 }
350 }
351 Self { coefficients }
352 }
353}
354
355impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for &Polynomial<F> {
356 type Output = Polynomial<F>;
357
358 fn mul(self, other: F) -> Self::Output {
359 Polynomial {
360 coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
361 }
362 }
363}
364
365impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for Polynomial<F> {
366 type Output = Polynomial<F>;
367
368 fn mul(self, other: F) -> Self::Output {
369 Polynomial {
370 coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
371 }
372 }
373}
374
375impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone>
376 Polynomial<F>
377{
378 pub fn karatsuba(&self, other: &Self) -> Self {
389 assert_eq!(
390 self.coefficients.len(),
391 other.coefficients.len(),
392 "karatsuba operands must have equal coefficient counts",
393 );
394 assert!(
395 karatsuba_length_is_supported(self.coefficients.len()),
396 "karatsuba operand length must be nonzero and stay even down to the base case (e.g. a power of two)",
397 );
398 Polynomial::new(vector_karatsuba(&self.coefficients, &other.coefficients))
399 }
400}
401
402impl<F> One for Polynomial<F>
403where
404 F: Clone + One + PartialEq + Zero + AddAssign,
405{
406 fn one() -> Self {
407 Self { coefficients: vec![F::one()] }
408 }
409}
410
411impl<F> Zero for Polynomial<F>
412where
413 F: Zero + PartialEq + Clone + AddAssign,
414{
415 fn zero() -> Self {
416 Self { coefficients: vec![] }
417 }
418
419 fn is_zero(&self) -> bool {
420 self.degree().is_none()
421 }
422}
423
424impl<F: Zero + Clone> Polynomial<F> {
425 pub fn shift(&self, shamt: usize) -> Self {
427 Self {
428 coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(),
429 }
430 }
431
432 pub fn constant(f: F) -> Self {
434 Self { coefficients: vec![f] }
435 }
436
437 pub fn map<G: Clone, C: FnMut(&F) -> G>(&self, closure: C) -> Polynomial<G> {
439 Polynomial::<G>::new(self.coefficients.iter().map(closure).collect())
440 }
441
442 pub fn fold<G, C: FnMut(G, &F) -> G + Clone>(&self, mut initial_value: G, closure: C) -> G {
444 for c in self.coefficients.iter() {
445 initial_value = (closure.clone())(initial_value, c);
446 }
447 initial_value
448 }
449}
450
451impl<F> Div<Polynomial<F>> for Polynomial<F>
452where
453 F: Zero
454 + One
455 + PartialEq
456 + AddAssign
457 + Clone
458 + Mul<Output = F>
459 + MulAssign
460 + Div<Output = F>
461 + Neg<Output = F>
462 + Sub<Output = F>,
463{
464 type Output = Polynomial<F>;
465
466 fn div(self, denominator: Self) -> Self::Output {
478 assert!(!denominator.is_zero(), "cannot divide a polynomial by the zero polynomial");
479 if self.is_zero() {
480 return Self::zero();
481 }
482 let mut remainder = self;
483 let mut quotient = Polynomial::<F>::zero();
484 while remainder.degree().unwrap() >= denominator.degree().unwrap() {
485 let degree_before = remainder.degree().unwrap();
486 let shift = degree_before - denominator.degree().unwrap();
487 let quotient_coefficient = remainder.lc() / denominator.lc();
488 let monomial = Self::constant(quotient_coefficient).shift(shift);
489 quotient += monomial.clone();
490 remainder -= monomial * denominator.clone();
491 if remainder.is_zero() {
492 break;
493 }
494 assert!(
495 remainder.degree().unwrap() < degree_before,
496 "inexact polynomial division: the leading-coefficient quotient did not cancel the leading term"
497 );
498 }
499 quotient
500 }
501}
502
503const fn karatsuba_length_is_supported(mut n: usize) -> bool {
508 if n == 0 {
509 return false;
510 }
511 while n > 8 {
512 if n % 2 == 1 {
513 return false;
514 }
515 n /= 2;
516 }
517 true
518}
519
520fn vector_karatsuba<
521 F: Zero + AddAssign + Mul<Output = F> + Sub<Output = F> + Div<Output = F> + Clone,
522>(
523 left: &[F],
524 right: &[F],
525) -> Vec<F> {
526 let n = left.len();
527 if n <= 8 {
528 let mut product = vec![F::zero(); left.len() + right.len() - 1];
529 for (i, l) in left.iter().enumerate() {
530 for (j, r) in right.iter().enumerate() {
531 product[i + j] += l.clone() * r.clone();
532 }
533 }
534 return product;
535 }
536 let n_over_2 = n / 2;
537 let mut product = vec![F::zero(); 2 * n - 1];
538 let left_lo = &left[0..n_over_2];
539 let right_lo = &right[0..n_over_2];
540 let left_hi = &left[n_over_2..];
541 let right_hi = &right[n_over_2..];
542 let left_sum: Vec<F> =
543 left_lo.iter().zip(left_hi).map(|(a, b)| a.clone() + b.clone()).collect();
544 let right_sum: Vec<F> =
545 right_lo.iter().zip(right_hi).map(|(a, b)| a.clone() + b.clone()).collect();
546
547 let prod_lo = vector_karatsuba(left_lo, right_lo);
548 let prod_hi = vector_karatsuba(left_hi, right_hi);
549 let prod_mid: Vec<F> = vector_karatsuba(&left_sum, &right_sum)
550 .iter()
551 .zip(prod_lo.iter().zip(prod_hi.iter()))
552 .map(|(s, (l, h))| s.clone() - (l.clone() + h.clone()))
553 .collect();
554
555 for (i, l) in prod_lo.into_iter().enumerate() {
556 product[i] = l;
557 }
558 for (i, m) in prod_mid.into_iter().enumerate() {
559 product[i + n_over_2] += m;
560 }
561 for (i, h) in prod_hi.into_iter().enumerate() {
562 product[i + n] += h
563 }
564 product
565}
566
567impl From<Polynomial<FalconFelt>> for Polynomial<Felt> {
568 fn from(item: Polynomial<FalconFelt>) -> Self {
569 let res: Vec<Felt> =
570 item.coefficients.iter().map(|a| Felt::from_u16(a.value() as u16)).collect();
571 Polynomial::new(res)
572 }
573}
574
575impl From<&Polynomial<FalconFelt>> for Polynomial<Felt> {
576 fn from(item: &Polynomial<FalconFelt>) -> Self {
577 let res: Vec<Felt> =
578 item.coefficients.iter().map(|a| Felt::from_u16(a.value() as u16)).collect();
579 Polynomial::new(res)
580 }
581}
582
583impl From<Polynomial<i16>> for Polynomial<FalconFelt> {
584 fn from(item: Polynomial<i16>) -> Self {
585 let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
586 Polynomial::new(res)
587 }
588}
589
590impl From<&Polynomial<i16>> for Polynomial<FalconFelt> {
591 fn from(item: &Polynomial<i16>) -> Self {
592 let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
593 Polynomial::new(res)
594 }
595}
596
597impl From<Vec<i16>> for Polynomial<FalconFelt> {
598 fn from(item: Vec<i16>) -> Self {
599 let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
600 Polynomial::new(res)
601 }
602}
603
604impl From<&Vec<i16>> for Polynomial<FalconFelt> {
605 fn from(item: &Vec<i16>) -> Self {
606 let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
607 Polynomial::new(res)
608 }
609}
610
611impl Polynomial<FalconFelt> {
612 pub fn norm_squared(&self) -> u64 {
614 self.coefficients
615 .iter()
616 .map(|&i| i.balanced_value() as i64)
617 .map(|i| (i * i) as u64)
618 .sum::<u64>()
619 }
620
621 pub fn to_elements(&self) -> Vec<Felt> {
626 self.coefficients.iter().map(|&a| Felt::from_u16(a.value() as u16)).collect()
627 }
628
629 pub fn to_balanced_values(&self) -> Vec<i16> {
631 self.coefficients.iter().copied().map(FalconFelt::balanced_value).collect()
632 }
633
634 pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] {
644 let mut c = [0; 2 * N];
645 for i in 0..N {
646 for j in 0..N {
647 c[i + j] += a.coefficients[i].value() as u64 * b.coefficients[j].value() as u64;
648 }
649 }
650
651 c
652 }
653
654 pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self {
657 let mut c = [FalconFelt::zero(); N];
658 let modulus = MODULUS as u16;
659 for i in 0..N {
660 let ai = a[N + i] % modulus as u64;
661 let neg_ai = (modulus - ai as u16) % modulus;
662
663 let bi = (a[i] % modulus as u64) as u16;
664 c[i] = FalconFelt::new(((neg_ai + bi) % modulus) as i16);
665 }
666
667 Self::new(c.to_vec())
668 }
669}
670
671impl Polynomial<Felt> {
672 pub fn to_elements(&self) -> Vec<Felt> {
674 self.coefficients.clone()
675 }
676}
677
678impl Polynomial<i16> {
679 pub fn to_balanced_values(&self) -> Vec<i16> {
681 self.coefficients.iter().map(|c| FalconFelt::new(*c).balanced_value()).collect()
682 }
683}
684
685impl<F: Zeroize> Zeroize for Polynomial<F> {
689 fn zeroize(&mut self) {
690 self.coefficients.zeroize();
691 }
692}
693
694#[cfg(test)]
698mod tests {
699 use proptest::{collection::vec, prelude::*};
700
701 use super::{FalconFelt, N, Polynomial};
702 use crate::rand::test_utils::prng_array;
703
704 #[test]
705 fn div_zero_by_nonzero_returns_zero() {
706 use num::Zero;
707 let zero = Polynomial::<i64>::zero();
708 let nonzero = Polynomial::new(vec![1, 2, 3]);
709 let result = zero / nonzero;
710 assert!(result.is_zero());
711 }
712
713 #[test]
714 fn div_exact_integer_division_returns_quotient() {
715 let numerator = Polynomial::new(vec![2i64, 3, 1]);
717 let denominator = Polynomial::new(vec![1i64, 1]);
718 assert_eq!((numerator / denominator).coefficients, vec![2, 1]);
719 }
720
721 #[test]
722 #[should_panic(expected = "inexact polynomial division")]
723 fn div_inexact_integer_division_panics_instead_of_looping() {
724 let numerator = Polynomial::new(vec![3i64]);
727 let denominator = Polynomial::new(vec![2i64]);
728 let _ = numerator / denominator;
729 }
730
731 #[test]
732 #[should_panic(expected = "zero polynomial")]
733 fn div_by_zero_panics_with_message() {
734 use num::Zero;
735 let numerator = Polynomial::new(vec![1i64]);
736 let _ = numerator / Polynomial::<i64>::zero();
737 }
738
739 #[test]
740 fn karatsuba_agrees_with_mul_for_representative_admissible_shapes() {
741 for n in [2i64, 4, 8, 16, 32, 10, 20, 24] {
744 let f = Polynomial::new((0..n).map(|i| i * i - 7 * i + 3).collect());
745 let g = Polynomial::new((0..n).map(|i| 5 * i - 11).collect());
746 let schoolbook = f.clone() * g.clone();
747 assert_eq!(f.karatsuba(&g), schoolbook, "karatsuba disagrees at n = {n}");
748 }
749 }
750
751 proptest! {
752 #[test]
753 fn karatsuba_agrees_with_mul_for_admissible_operands(
754 (left, right) in (1usize..=8, 0u32..=6).prop_flat_map(|(base, doublings)| {
755 let len = base << doublings;
756 (vec(-1_000i64..=1_000, len), vec(-1_000i64..=1_000, len))
757 }),
758 ) {
759 let left = Polynomial::new(left);
760 let right = Polynomial::new(right);
761 prop_assert_eq!(left.karatsuba(&right), left * right);
762 }
763 }
764
765 #[test]
766 #[should_panic(expected = "karatsuba operand length")]
767 fn karatsuba_rejects_empty_operands() {
768 let empty = Polynomial::<i64>::new(vec![]);
769 let _ = empty.karatsuba(&empty.clone());
770 }
771
772 #[test]
773 fn karatsuba_length_support_matches_the_recursion_shape() {
774 use super::karatsuba_length_is_supported;
775 for supported in [1usize, 2, 5, 8, 10, 20, 24, 64, 512] {
776 assert!(karatsuba_length_is_supported(supported), "{supported} must be supported");
777 }
778 for unsupported in [0usize, 9, 11, 17, 18, 34, 513] {
779 assert!(
780 !karatsuba_length_is_supported(unsupported),
781 "{unsupported} must be unsupported"
782 );
783 }
784 }
785
786 #[test]
787 #[should_panic(expected = "karatsuba operand length")]
788 fn karatsuba_rejects_odd_lengths_above_the_base_case() {
789 let f = Polynomial::new(vec![1i64; 9]);
790 let _ = f.karatsuba(&f.clone());
791 }
792
793 #[test]
794 #[should_panic(expected = "equal coefficient counts")]
795 fn karatsuba_rejects_unequal_lengths() {
796 let f = Polynomial::new(vec![1i64; 8]);
797 let g = Polynomial::new(vec![1i64; 4]);
798 let _ = f.karatsuba(&g);
799 }
800
801 #[test]
802 fn galois_adjoint_negates_odd_degree_coefficients() {
803 let f = Polynomial::new(vec![1i64, 2, 3, 4]);
804 assert_eq!(f.galois_adjoint().coefficients, vec![1, -2, 3, -4]);
805 }
806
807 #[test]
808 fn galois_adjoint_product_with_self_is_even() {
809 let f = Polynomial::new(vec![3i64, -1, 4, 1, -5, 9, -2, 6]);
812 let product = f.clone() * f.galois_adjoint();
813 for (degree, c) in product.coefficients.iter().enumerate() {
814 if degree % 2 == 1 {
815 assert_eq!(*c, 0, "odd-degree coefficient {degree} must vanish");
816 }
817 }
818 }
819
820 #[test]
821 fn test_negacyclic_reduction() {
822 let coef1: [u8; N] = prng_array([0u8; 32]);
823 let coef2: [u8; N] = prng_array([1u8; 32]);
824
825 let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect());
826 let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect());
827 let prod = poly1.clone() * poly2.clone();
828
829 assert_eq!(
830 prod.reduce_by_cyclotomic(N),
831 Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2))
832 );
833 }
834}