use crate::num::arithmetic::mod_mul::{
mod_mul_precompute_shoup, mod_mul_shoup, mod_mul_shoup_lazy,
};
use crate::num::arithmetic::traits::{ModIsReduced, ModPowerOf2IsReduced};
use crate::num::basic::integers::PrimitiveInt;
use crate::num::basic::unsigneds::PrimitiveUnsigned;
use crate::num::conversion::traits::ExactFrom;
use crate::polynomial::{ModEvaluate, ModEvaluateGeometric, ModEvaluateMany, ModPowerOf2Evaluate};
use crate::unsigned_polynomial::UnsignedPolynomial;
use crate::unsigned_polynomial::arithmetic::mod_mul::ModData;
use crate::unsigned_polynomial::arithmetic::mod_mul_middle::mod_mul_middle_karatsuba;
use crate::unsigned_polynomial::arithmetic::mod_mul_truncated::mod_mul_truncated_to_out;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::max;
fn mod_power_of_2_evaluate<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, x: T, pow: u64) -> T {
assert!(pow <= T::WIDTH);
assert!(
p.mod_power_of_2_is_reduced(pow),
"self must be reduced mod 2^pow, but {p} has a coefficient >= 2^{pow}"
);
assert!(
x.significant_bits() <= pow,
"x must be reduced mod 2^pow, but {x} >= 2^{pow}"
);
let mut value = T::ZERO;
for &c in p.coefficients.iter().rev() {
value = value.wrapping_mul(x).wrapping_add(c);
}
value.mod_power_of_2(pow)
}
impl<T: PrimitiveUnsigned> ModPowerOf2Evaluate<T> for &UnsignedPolynomial<T> {
type Output = T;
#[inline]
fn mod_power_of_2_evaluate(self, x: T, pow: u64) -> T {
mod_power_of_2_evaluate(self, x, pow)
}
}
impl<T: PrimitiveUnsigned> ModPowerOf2Evaluate<T> for UnsignedPolynomial<T> {
type Output = T;
#[inline]
fn mod_power_of_2_evaluate(self, x: T, pow: u64) -> T {
mod_power_of_2_evaluate(&self, x, pow)
}
}
const MOD_EVALUATE_SHOUP_THRESHOLD: usize = 3;
crate_test_fn! {mod_evaluate_horner<T: PrimitiveUnsigned>(coefficients: &[T], x: T, m: T) -> T {
let data = T::precompute_mod_mul_data(&m);
let (&last, rest) = coefficients.split_last().unwrap();
let mut value = last;
for &c in rest.iter().rev() {
value.mod_mul_precomputed_assign(x, m, &data);
value.mod_add_assign(c, m);
}
value
}}
crate_test_fn! {mod_evaluate_shoup<T: PrimitiveUnsigned>(
coefficients: &[T],
x: T,
x_precomp: T,
m: T,
) -> T {
let (&last, rest) = coefficients.split_last().unwrap();
let mut value = last;
for &c in rest.iter().rev() {
value = mod_mul_shoup(x, value, x_precomp, m);
value.mod_add_assign(c, m);
}
value
}}
crate_test_fn! {mod_evaluate_shoup_lazy<T: PrimitiveUnsigned>(
coefficients: &[T],
x: T,
x_precomp: T,
m: T,
) -> T {
let (&last, rest) = coefficients.split_last().unwrap();
let mut value = last;
for &c in rest.iter().rev() {
value = mod_mul_shoup_lazy(x, value, x_precomp, m);
value.wrapping_add_assign(c);
}
value
}}
#[doc(hidden)]
pub fn mod_evaluate_slice<T: PrimitiveUnsigned>(coefficients: &[T], x: T, m: T) -> T {
let len = coefficients.len();
if len == 0 {
return T::ZERO;
}
if len == 1 || x == T::ZERO {
return coefficients[0];
}
if m.get_highest_bit() || (T::WIDTH > u32::WIDTH && len < MOD_EVALUATE_SHOUP_THRESHOLD) {
return mod_evaluate_horner(coefficients, x, m);
}
let x_precomp = mod_mul_precompute_shoup(x, m);
if m <= T::MAX / T::from(3u8) {
let mut value = mod_evaluate_shoup_lazy(coefficients, x, x_precomp, m);
let two_m = m << 1;
if value >= two_m {
value -= two_m;
} else if value >= m {
value -= m;
}
value
} else {
mod_evaluate_shoup(coefficients, x, x_precomp, m)
}
}
fn mod_evaluate<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, x: T, m: T) -> T {
assert!(
p.mod_is_reduced(&m),
"self must be reduced mod m, but {p} has a coefficient >= {m}"
);
assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
mod_evaluate_slice(&p.coefficients, x, m)
}
impl<T: PrimitiveUnsigned> ModEvaluate<T> for &UnsignedPolynomial<T> {
type Output = T;
#[inline]
fn mod_evaluate(self, x: T, m: T) -> T {
mod_evaluate(self, x, m)
}
}
impl<T: PrimitiveUnsigned> ModEvaluate<T> for UnsignedPolynomial<T> {
type Output = T;
#[inline]
fn mod_evaluate(self, x: T, m: T) -> T {
mod_evaluate(&self, x, m)
}
}
const MOD_EVALUATE_MANY_NARROW_BLOCK: usize = 4;
const MOD_EVALUATE_MANY_WIDE_BLOCK: usize = 8;
crate_test_fn! {mod_evaluate_horner_block<T: PrimitiveUnsigned, const N: usize>(
coefficients: &[T],
xs: &mut [T; N],
m: T,
) {
let data = T::precompute_mod_mul_data(&m);
let (&last, rest) = coefficients.split_last().unwrap();
let points = *xs;
let mut values = [last; N];
for &c in rest.iter().rev() {
for (value, &x) in values.iter_mut().zip(points.iter()) {
value.mod_mul_precomputed_assign(x, m, &data);
value.mod_add_assign(c, m);
}
}
*xs = values;
}}
crate_test_fn! {mod_evaluate_shoup_block<T: PrimitiveUnsigned, const N: usize>(
coefficients: &[T],
xs: &mut [T; N],
m: T,
) {
let points = *xs;
let precomps = points.map(|x| mod_mul_precompute_shoup(x, m));
let (&last, rest) = coefficients.split_last().unwrap();
let mut values = [last; N];
for &c in rest.iter().rev() {
for ((value, &x), &x_precomp) in values.iter_mut().zip(points.iter()).zip(precomps.iter()) {
*value = mod_mul_shoup(x, *value, x_precomp, m);
value.mod_add_assign(c, m);
}
}
*xs = values;
}}
crate_test_fn! {mod_evaluate_shoup_lazy_block<T: PrimitiveUnsigned, const N: usize>(
coefficients: &[T],
xs: &mut [T; N],
m: T,
) {
let points = *xs;
let precomps = points.map(|x| mod_mul_precompute_shoup(x, m));
let (&last, rest) = coefficients.split_last().unwrap();
let mut values = [last; N];
for &c in rest.iter().rev() {
for ((value, &x), &x_precomp) in values.iter_mut().zip(points.iter()).zip(precomps.iter()) {
*value = mod_mul_shoup_lazy(x, *value, x_precomp, m);
value.wrapping_add_assign(c);
}
}
let two_m = m << 1;
for value in &mut values {
if *value >= two_m {
*value -= two_m;
} else if *value >= m {
*value -= m;
}
}
*xs = values;
}}
fn mod_evaluate_many_blocks<'a, T: PrimitiveUnsigned, const N: usize>(
coefficients: &[T],
xs: &'a mut [T],
m: T,
shoup: bool,
lazy: bool,
) -> &'a mut [T] {
let (blocks, remainder) = xs.as_chunks_mut::<N>();
for block in blocks {
if !shoup {
mod_evaluate_horner_block(coefficients, block, m);
} else if lazy {
mod_evaluate_shoup_lazy_block(coefficients, block, m);
} else {
mod_evaluate_shoup_block(coefficients, block, m);
}
}
remainder
}
crate_test_fn! {mod_evaluate_many_in_place<T: PrimitiveUnsigned>(
coefficients: &[T],
xs: &mut [T],
m: T,
) {
let len = coefficients.len();
match len {
0 => xs.fill(T::ZERO),
1 => xs.fill(coefficients[0]),
_ => {
let shoup = !m.get_highest_bit()
&& (T::WIDTH <= u32::WIDTH || len >= MOD_EVALUATE_SHOUP_THRESHOLD);
let lazy = m <= T::MAX / T::from(3u8);
let xs = if T::WIDTH <= u32::WIDTH {
xs
} else {
mod_evaluate_many_blocks::<T, MOD_EVALUATE_MANY_WIDE_BLOCK>(
coefficients,
xs,
m,
shoup,
lazy,
)
};
let remainder = mod_evaluate_many_blocks::<T, MOD_EVALUATE_MANY_NARROW_BLOCK>(
coefficients,
xs,
m,
shoup,
lazy,
);
for x in remainder {
*x = mod_evaluate_slice(coefficients, *x, m);
}
}
}
}}
impl<T: PrimitiveUnsigned> ModEvaluateMany<T> for &UnsignedPolynomial<T> {
type Output = T;
fn mod_evaluate_many(self, xs: &[T], m: T) -> Vec<T> {
assert!(
self.mod_is_reduced(&m),
"self must be reduced mod m, but {self} has a coefficient >= {m}"
);
for &x in xs {
assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
}
let mut values = xs.to_vec();
mod_evaluate_many_in_place(&self.coefficients, &mut values, m);
values
}
}
fn mod_evaluate_geometric_fast_preferred<T: PrimitiveUnsigned>(n: usize, k: usize, m: T) -> bool {
let bits = m.significant_bits();
let width = T::WIDTH;
let (min_n, min_k) = if bits == width {
(32, 32)
} else if bits == width - 1 {
(256, 256)
} else if bits + 8 > width {
(512, 1024)
} else if bits << 4 > width * 5 {
(256, 256)
} else {
(128, 128)
};
n >= min_n && k >= min_k
}
crate_test_fn! {mod_evaluate_geometric_iter<T: PrimitiveUnsigned>(
coefficients: &[T],
q: T,
k: usize,
m: T,
) -> Vec<T> {
let mut values = Vec::with_capacity(k);
if k != 0 {
let mut power = T::ONE % m;
values.push(power);
if m.get_highest_bit() {
let data = T::precompute_mod_mul_data(&m);
for _ in 1..k {
power.mod_mul_precomputed_assign(q, m, &data);
values.push(power);
}
} else {
let q_precomp = mod_mul_precompute_shoup(q, m);
for _ in 1..k {
power = mod_mul_shoup(q, power, q_precomp, m);
values.push(power);
}
}
}
mod_evaluate_many_in_place(coefficients, &mut values, m);
values
}}
crate_test_fn! {mod_evaluate_geometric_fast<T: PrimitiveUnsigned>(
coefficients: &[T],
q: T,
q_inverse: T,
k: usize,
m: T,
) -> Vec<T> {
if k == 0 {
return Vec::new();
}
let Some(start) = coefficients.iter().position(|&c| c != T::ZERO) else {
return vec![T::ZERO; k];
};
let n = coefficients.len();
let a_len = n - start;
let data = T::precompute_mod_mul_data(&m);
let b_len = n + k - 1;
let mut binomial_powers = Vec::with_capacity(b_len);
let mut power = T::ONE % m;
let mut step = T::ONE % m;
for _ in 0..b_len {
binomial_powers.push(power);
power.mod_mul_precomputed_assign(step, m, &data);
step.mod_mul_precomputed_assign(q, m, &data);
}
let w_len = max(n, k);
let mut inverse_powers = Vec::with_capacity(w_len);
let mut power = T::ONE % m;
let mut step = T::ONE % m;
for _ in 0..w_len {
inverse_powers.push(power);
power.mod_mul_precomputed_assign(step, m, &data);
step.mod_mul_precomputed_assign(q_inverse, m, &data);
}
let scaled: Vec<T> = coefficients[start..]
.iter()
.zip(&inverse_powers[start..])
.rev()
.map(|(&c, &w)| c.mod_mul_precomputed(w, m, &data))
.collect();
let mut product = vec![T::ZERO; k];
mod_mul_middle_karatsuba(
&mut product,
&scaled,
&binomial_powers[start..start + a_len + k - 1],
&ModData::new(m, a_len),
);
product
.iter()
.zip(&inverse_powers)
.map(|(&z, &w)| z.mod_mul_precomputed(w, m, &data))
.collect()
}}
crate_test_fn! {
#[allow(dead_code)]
mod_evaluate_geometric_fast_truncated<T: PrimitiveUnsigned>(
coefficients: &[T],
q: T,
q_inverse: T,
k: usize,
m: T,
) -> Vec<T> {
if k == 0 {
return Vec::new();
}
let Some(start) = coefficients.iter().position(|&c| c != T::ZERO) else {
return vec![T::ZERO; k];
};
let n = coefficients.len();
let a_len = n - start;
let data = T::precompute_mod_mul_data(&m);
let b_len = n + k - 1;
let mut binomial_powers = Vec::with_capacity(b_len);
let mut power = T::ONE % m;
let mut step = T::ONE % m;
for _ in 0..b_len {
binomial_powers.push(power);
power.mod_mul_precomputed_assign(step, m, &data);
step.mod_mul_precomputed_assign(q, m, &data);
}
let w_len = max(n, k);
let mut inverse_powers = Vec::with_capacity(w_len);
let mut power = T::ONE % m;
let mut step = T::ONE % m;
for _ in 0..w_len {
inverse_powers.push(power);
power.mod_mul_precomputed_assign(step, m, &data);
step.mod_mul_precomputed_assign(q_inverse, m, &data);
}
let scaled: Vec<T> = coefficients[start..]
.iter()
.zip(&inverse_powers[start..])
.rev()
.map(|(&c, &w)| c.mod_mul_precomputed(w, m, &data))
.collect();
let mut product = vec![T::ZERO; a_len + k - 1];
mod_mul_truncated_to_out(
&mut product,
&scaled,
&binomial_powers[start..start + a_len + k - 1],
m,
);
product[a_len - 1..]
.iter()
.zip(&inverse_powers)
.map(|(&z, &w)| z.mod_mul_precomputed(w, m, &data))
.collect()
}}
impl<T: PrimitiveUnsigned> ModEvaluateGeometric<T> for &UnsignedPolynomial<T> {
type Output = T;
fn mod_evaluate_geometric(self, q: T, k: u64, m: T) -> Vec<T> {
assert!(
self.mod_is_reduced(&m),
"self must be reduced mod m, but {self} has a coefficient >= {m}"
);
assert!(q < m, "q must be reduced mod m, but {q} >= {m}");
let k = usize::exact_from(k);
let n = self.coefficients.len();
if mod_evaluate_geometric_fast_preferred(n, k, m)
&& q != T::ZERO
&& let Some(q_inverse) = q.mod_inverse(m)
{
mod_evaluate_geometric_fast(&self.coefficients, q, q_inverse, k, m)
} else {
mod_evaluate_geometric_iter(&self.coefficients, q, k, m)
}
}
}