use std::ops::{Add, Mul, Sub};
use num::{One, Zero};
use crate::{modulo::PolynomialModulus, Polynomial};
pub(crate) fn add<T, const N: usize>(a: Polynomial<T, N>, b: Polynomial<T, N>) -> Polynomial<T, N>
where
T: Zero,
for<'a> &'a T: Add<Output = T>,
{
let (a, b) = if a.coeffs.len() < b.coeffs.len() {
(b, a)
} else {
(a, b)
};
let mut coeffs = a.coeffs;
coeffs
.iter_mut()
.zip(b.coeffs.iter())
.for_each(|(a_i, b_i)| {
*a_i = &*a_i + b_i;
});
trim_zeros(&mut coeffs);
Polynomial { coeffs }
}
#[allow(clippy::needless_range_loop)] pub(crate) fn cyclic_mul<T, const N: usize>(
a: &Polynomial<T, N>,
b: &Polynomial<T, N>,
) -> Polynomial<T, N>
where
T: Zero + Clone,
for<'a> &'a T: Add<Output = T> + Mul<Output = T> + Sub<Output = T>,
{
let mut coeffs = vec![T::zero(); N];
for j in 0..b.coeffs.len() {
let b_coeff = &b.coeffs[j];
for i in 0..j {
if let Some(a_coeff) = a.coeffs.get(N - j + i) {
coeffs[i] = &coeffs[i] - &(a_coeff * b_coeff);
} else {
break;
}
}
for i in j..N {
if let Some(a_coeff) = a.coeffs.get(i - j) {
coeffs[i] = &coeffs[i] + &(a_coeff * b_coeff);
} else {
break;
}
}
}
trim_zeros(&mut coeffs);
Polynomial { coeffs }
}
pub(crate) fn modulo<T, const N: usize>(
p: Polynomial<T, N>,
modulus: PolynomialModulus<T>,
) -> Polynomial<T, N>
where
T: Zero + One + Clone,
for<'a> &'a T: Mul<Output = T> + Sub<Output = T> + Add<Output = T>,
{
if p.is_zero() {
return Polynomial::zero();
}
if p.deg() < modulus.deg() {
return p;
}
let mut r = p; let s = modulus.deg();
let b_s = modulus.leading_coefficient();
while !r.is_zero() && r.deg() >= s {
let pow_y = r.deg() - s;
let lc_r = r.coeffs.last().cloned().unwrap();
for i in 0..r.deg() {
let term: T = if i < pow_y {
T::zero()
} else {
&lc_r * &modulus.coefficient(i - pow_y)
};
r.coeffs[i] = &(&b_s * &r.coeffs[i]) - &term;
}
r.coeffs.pop();
trim_zeros(&mut r.coeffs);
}
r
}
pub(crate) fn trim_zeros<T: Zero>(v: &mut Vec<T>) {
while let Some(&t) = v.last().as_ref() {
if t.is_zero() {
v.pop();
} else {
break;
}
}
}
#[cfg(test)]
mod tests {
use std::ops::Neg;
use rand::RngExt;
use super::*;
const N: usize = 512;
#[test]
fn test_trim_zeros() {
let mut coeffs = vec![1, 2, 3, 0, 0, 0];
trim_zeros(&mut coeffs);
assert_eq!(coeffs, vec![1, 2, 3]);
let mut coeffs = vec![0, 0, 0];
trim_zeros(&mut coeffs);
assert_eq!(coeffs, vec![]);
}
#[test]
fn test_add() {
let p1 = Polynomial::<i32, N>::new(vec![1, 2, 3]);
let p2 = Polynomial::new(vec![4, 5]);
let r = add(p1, p2);
assert_eq!(r.coeffs, vec![5, 7, 3]);
let p1 = Polynomial::<i32, N>::new(vec![2, 1]);
let p2 = Polynomial::new(vec![5, 4, 3]);
let r = add(p1, p2);
assert_eq!(r.coeffs, vec![7, 5, 3]);
let p1 = Polynomial::<i32, N>::new(vec![1, 2, 3]);
let p2 = Polynomial::zero();
let r = add(p1, p2);
assert_eq!(r.coeffs, vec![1, 2, 3]);
let p1 = Polynomial::<i32, N>::zero();
let p2 = Polynomial::new(vec![1, 2, 3]);
let r = add(p1, p2);
assert_eq!(r.coeffs, vec![1, 2, 3]);
}
#[test]
fn test_neg() {
let p1 = Polynomial::<i32, N>::new(vec![1, 2, 3]);
let p2 = Polynomial::new(vec![4, 5]);
let r = add(p1, p2.neg());
assert_eq!(r.coeffs, vec![-3, -3, 3]);
let p1 = Polynomial::<i32, N>::new(vec![2, 1]);
let p2 = Polynomial::new(vec![5, 4, 3]);
let r = add(p1, p2.neg());
assert_eq!(r.coeffs, vec![-3, -3, -3]);
let p1 = Polynomial::<i32, N>::new(vec![1, 2, 3]);
let p2 = Polynomial::zero();
let r = add(p1, p2.neg());
assert_eq!(r.coeffs, vec![1, 2, 3]);
let p1 = Polynomial::<i32, N>::zero();
let p2 = Polynomial::new(vec![1, 2, 3]);
let r = add(p1, p2.neg());
assert_eq!(r.coeffs, vec![-1, -2, -3]);
}
#[test]
fn test_modulo() {
let r = Polynomial::<i32, 4>::from_coeffs(vec![1, 2, 3, 4, 5]);
assert_eq!(r.coeffs, vec![-4, 2, 3, 4]);
}
#[test]
fn test_cyclic_mul() {
let rng = &mut rand::rng();
let a =
Polynomial::<i32, N>::new((0..N).map(|_| rng.random_range(0..100)).collect::<Vec<_>>());
let p = Polynomial::<i32, N>::new(vec![0, 1]);
let rhs = cyclic_mul(&a, &p);
assert!(a.coefficient(N - 1) == -&rhs.coefficient(0));
for i in 0..N - 1 {
assert!(a.coefficient(i) == rhs.coefficient(i + 1));
}
}
}