use crate::num::arithmetic::traits::{ModMul, ModMulAssign};
use crate::num::basic::traits::Zero;
use crate::num::basic::unsigneds::PrimitiveUnsigned;
use crate::unsigned_polynomial::UnsignedPolynomial;
use crate::unsigned_polynomial::arithmetic::mod_add::assert_reduced;
use crate::unsigned_polynomial::arithmetic::mod_power_of_2_mul::from_coefficients_trimmed;
use crate::unsigned_vector::arithmetic::mod_add::mod_add_assign_slice;
use crate::unsigned_vector::arithmetic::mod_dot::{ModData, column_sum};
use crate::unsigned_vector::arithmetic::mod_sub::mod_sub_assign_slice;
use alloc::vec;
pub(crate) const MOD_MUL_KARATSUBA_THRESHOLD: usize = 80;
pub(crate) const MOD_SQUARE_KARATSUBA_THRESHOLD: usize = 256;
pub(crate) fn mod_mul_classical<T: PrimitiveUnsigned>(
out: &mut [T],
xs: &[T],
ys: &[T],
d: &ModData<T>,
) {
let n = xs.len();
let m = ys.len();
for (k, o) in out.iter_mut().enumerate() {
let start = k.saturating_sub(m - 1);
let stop = core::cmp::min(k, n - 1);
let acc = column_sum(&xs[start..=stop], &ys[k - stop..=k - start], d);
*o = d.reduce_sum(acc);
}
}
pub(crate) const fn mod_karatsuba_scratch_len(mut n: usize, threshold: usize) -> usize {
let mut len = 0;
while n >= threshold {
let c = n - (n >> 1);
len += (c << 2) - 1;
n = c;
}
len
}
fn mod_mul_karatsuba_balanced<T: PrimitiveUnsigned>(
out: &mut [T],
xs: &[T],
ys: &[T],
d: &ModData<T>,
scratch: &mut [T],
) {
let n = xs.len();
if n < MOD_MUL_KARATSUBA_THRESHOLD {
mod_mul_classical(out, xs, ys, d);
return;
}
let m = d.m;
let h = n >> 1;
let c = n - h;
let two_h = h << 1;
let (x0, x1) = xs.split_at(h);
let (y0, y1) = ys.split_at(h);
split_into_chunks_mut!(scratch, c, [x_sum, y_sum], scratch);
let (middle, scratch) = scratch.split_at_mut((c << 1) - 1);
let (low, high) = out.split_at_mut(two_h);
mod_mul_karatsuba_balanced(&mut low[..two_h - 1], x0, y0, d, scratch);
low[two_h - 1] = T::ZERO;
mod_mul_karatsuba_balanced(high, x1, y1, d, scratch);
x_sum.copy_from_slice(x1);
mod_add_assign_slice(x_sum, x0, m);
y_sum.copy_from_slice(y1);
mod_add_assign_slice(y_sum, y0, m);
mod_mul_karatsuba_balanced(middle, x_sum, y_sum, d, scratch);
mod_sub_assign_slice(middle, &out[..two_h - 1], m);
mod_sub_assign_slice(middle, &out[two_h..], m);
mod_add_assign_slice(&mut out[h..], middle, m);
}
pub(crate) fn mod_mul_karatsuba<T: PrimitiveUnsigned>(
out: &mut [T],
xs: &[T],
ys: &[T],
d: &ModData<T>,
) {
let (xs, ys) = if xs.len() >= ys.len() {
(xs, ys)
} else {
(ys, xs)
};
let n = xs.len();
let m = ys.len();
if m < MOD_MUL_KARATSUBA_THRESHOLD {
mod_mul_classical(out, xs, ys, d);
return;
}
let mut scratch = vec![T::ZERO; mod_karatsuba_scratch_len(m, MOD_MUL_KARATSUBA_THRESHOLD)];
if n == m {
mod_mul_karatsuba_balanced(out, xs, ys, d, &mut scratch);
return;
}
out.fill(T::ZERO);
let mut product = vec![T::ZERO; (m << 1) - 1];
for (k, piece) in xs.chunks(m).enumerate() {
let product = &mut product[..piece.len() + m - 1];
if piece.len() == m {
mod_mul_karatsuba_balanced(product, piece, ys, d, &mut scratch);
} else {
mod_mul_karatsuba(product, ys, piece, d);
}
mod_add_assign_slice(&mut out[k * m..], product, d.m);
}
}
fn assert_lengths<T>(out: &[T], xs: &[T], ys: &[T]) {
assert!(!xs.is_empty());
assert!(!ys.is_empty());
assert_eq!(out.len(), xs.len() + ys.len() - 1);
}
crate_test_fn! {
#[allow(dead_code)]
mod_mul_to_out_classical<T: PrimitiveUnsigned>(out: &mut [T], xs: &[T], ys: &[T], m: T) {
assert_lengths(out, xs, ys);
mod_mul_classical(out, xs, ys, &ModData::new(m, xs.len().min(ys.len())));
}}
crate_test_fn! {
#[allow(dead_code)]
mod_mul_to_out_karatsuba<T: PrimitiveUnsigned>(out: &mut [T], xs: &[T], ys: &[T], m: T) {
assert_lengths(out, xs, ys);
mod_mul_karatsuba(out, xs, ys, &ModData::new(m, xs.len().min(ys.len())));
}}
#[doc(hidden)]
pub fn mod_mul_to_out<T: PrimitiveUnsigned>(out: &mut [T], xs: &[T], ys: &[T], m: T) {
assert_lengths(out, xs, ys);
mod_mul_karatsuba(out, xs, ys, &ModData::new(m, xs.len().min(ys.len())));
}
pub(crate) fn mod_mul_helper<T: PrimitiveUnsigned>(
xs: &[T],
ys: &[T],
m: T,
) -> UnsignedPolynomial<T> {
if xs.is_empty() || ys.is_empty() {
return UnsignedPolynomial::ZERO;
}
let mut out = vec![T::ZERO; xs.len() + ys.len() - 1];
mod_mul_to_out(&mut out, xs, ys, m);
from_coefficients_trimmed(out)
}
impl<T: PrimitiveUnsigned> ModMul<Self, T> for UnsignedPolynomial<T> {
type Output = Self;
fn mod_mul(self, other: Self, m: T) -> Self {
assert_reduced(&self, &other, m);
mod_mul_helper(&self.coefficients, &other.coefficients, m)
}
}
impl<T: PrimitiveUnsigned> ModMul<&Self, T> for UnsignedPolynomial<T> {
type Output = Self;
fn mod_mul(self, other: &Self, m: T) -> Self {
assert_reduced(&self, other, m);
mod_mul_helper(&self.coefficients, &other.coefficients, m)
}
}
impl<T: PrimitiveUnsigned> ModMul<UnsignedPolynomial<T>, T> for &UnsignedPolynomial<T> {
type Output = UnsignedPolynomial<T>;
fn mod_mul(self, other: UnsignedPolynomial<T>, m: T) -> UnsignedPolynomial<T> {
assert_reduced(self, &other, m);
mod_mul_helper(&self.coefficients, &other.coefficients, m)
}
}
impl<T: PrimitiveUnsigned> ModMul<&UnsignedPolynomial<T>, T> for &UnsignedPolynomial<T> {
type Output = UnsignedPolynomial<T>;
fn mod_mul(self, other: &UnsignedPolynomial<T>, m: T) -> UnsignedPolynomial<T> {
assert_reduced(self, other, m);
mod_mul_helper(&self.coefficients, &other.coefficients, m)
}
}
impl<T: PrimitiveUnsigned> ModMulAssign<Self, T> for UnsignedPolynomial<T> {
fn mod_mul_assign(&mut self, other: Self, m: T) {
assert_reduced(self, &other, m);
*self = mod_mul_helper(&self.coefficients, &other.coefficients, m);
}
}
impl<T: PrimitiveUnsigned> ModMulAssign<&Self, T> for UnsignedPolynomial<T> {
fn mod_mul_assign(&mut self, other: &Self, m: T) {
assert_reduced(self, other, m);
*self = mod_mul_helper(&self.coefficients, &other.coefficients, m);
}
}