malachite_base/unsigned_polynomial/arithmetic/evaluate.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::num::arithmetic::mod_mul::{
10 mod_mul_precompute_shoup, mod_mul_shoup, mod_mul_shoup_lazy,
11};
12use crate::num::arithmetic::traits::{ModIsReduced, ModPowerOf2IsReduced};
13use crate::num::basic::integers::PrimitiveInt;
14use crate::num::basic::unsigneds::PrimitiveUnsigned;
15use crate::num::conversion::traits::ExactFrom;
16use crate::polynomial::{ModEvaluate, ModEvaluateGeometric, ModEvaluateMany, ModPowerOf2Evaluate};
17use crate::unsigned_polynomial::UnsignedPolynomial;
18use crate::unsigned_polynomial::arithmetic::mod_mul::ModData;
19use crate::unsigned_polynomial::arithmetic::mod_mul_middle::mod_mul_middle_karatsuba;
20use crate::unsigned_polynomial::arithmetic::mod_mul_truncated::mod_mul_truncated_to_out;
21use alloc::vec;
22use alloc::vec::Vec;
23use core::cmp::max;
24
25// Evaluates a polynomial at x modulo 2^pow, after checking that pow fits in `T` and that the
26// coefficients and x are reduced.
27fn mod_power_of_2_evaluate<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, x: T, pow: u64) -> T {
28 assert!(pow <= T::WIDTH);
29 assert!(
30 p.mod_power_of_2_is_reduced(pow),
31 "self must be reduced mod 2^pow, but {p} has a coefficient >= 2^{pow}"
32 );
33 assert!(
34 x.significant_bits() <= pow,
35 "x must be reduced mod 2^pow, but {x} >= 2^{pow}"
36 );
37 let mut value = T::ZERO;
38 for &c in p.coefficients.iter().rev() {
39 value = value.wrapping_mul(x).wrapping_add(c);
40 }
41 value.mod_power_of_2(pow)
42}
43
44impl<T: PrimitiveUnsigned> ModPowerOf2Evaluate<T> for &UnsignedPolynomial<T> {
45 type Output = T;
46
47 /// Evaluates an [`UnsignedPolynomial`] at a value of its coefficient type, modulo $2^k$, taking
48 /// the polynomial by reference. The coefficients and the value must already be reduced modulo
49 /// $2^k$, and $k$ may be at most the width of the type.
50 ///
51 /// $$
52 /// f(p, x, k) = \sum_{i=0}^{n-1} c_i x^i \bmod 2^k,
53 /// $$
54 ///
55 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
56 /// evaluates to 0 everywhere.
57 ///
58 /// Reducing modulo $2^k$ commutes with wrapping arithmetic, which works modulo $2^w$ for the
59 /// type's width $w \geq k$, so Horner's rule is carried out with wrapping multiplications and
60 /// additions and the value is reduced once, at the end.
61 ///
62 /// # Worst-case complexity
63 /// $T(n) = O(n)$
64 ///
65 /// $M(n) = O(1)$
66 ///
67 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
68 ///
69 /// # Panics
70 /// Panics if `pow` is greater than `T::WIDTH`, or if any coefficient of `self` or `x` is
71 /// greater than or equal to $2^k$.
72 ///
73 /// # Examples
74 /// ```
75 /// use core::str::FromStr;
76 /// use malachite_base::polynomial::ModPowerOf2Evaluate;
77 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
78 ///
79 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
80 /// // 5 * 36 + 3 * 6 + 7 = 205, which is 13 mod 16.
81 /// assert_eq!((&p).mod_power_of_2_evaluate(6, 4), 13);
82 /// assert_eq!((&p).mod_power_of_2_evaluate(0, 4), 7);
83 /// // All 8 bits of a u8: 205 itself.
84 /// assert_eq!((&p).mod_power_of_2_evaluate(6, 8), 205);
85 /// ```
86 ///
87 /// This is equivalent to `nmod_poly_evaluate_nmod` from `nmod_poly/evaluate_nmod.c`, FLINT
88 /// 3.6.0, with the modulus $2^k$, except that the value must be reduced.
89 #[inline]
90 fn mod_power_of_2_evaluate(self, x: T, pow: u64) -> T {
91 mod_power_of_2_evaluate(self, x, pow)
92 }
93}
94
95impl<T: PrimitiveUnsigned> ModPowerOf2Evaluate<T> for UnsignedPolynomial<T> {
96 type Output = T;
97
98 /// Evaluates an [`UnsignedPolynomial`] at a value of its coefficient type, modulo $2^k$, taking
99 /// the polynomial by value. The coefficients and the value must already be reduced modulo
100 /// $2^k$, and $k$ may be at most the width of the type.
101 ///
102 /// $$
103 /// f(p, x, k) = \sum_{i=0}^{n-1} c_i x^i \bmod 2^k,
104 /// $$
105 ///
106 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
107 /// evaluates to 0 everywhere.
108 ///
109 /// Reducing modulo $2^k$ commutes with wrapping arithmetic, which works modulo $2^w$ for the
110 /// type's width $w \geq k$, so Horner's rule is carried out with wrapping multiplications and
111 /// additions and the value is reduced once, at the end.
112 ///
113 /// # Worst-case complexity
114 /// $T(n) = O(n)$
115 ///
116 /// $M(n) = O(1)$
117 ///
118 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
119 ///
120 /// # Panics
121 /// Panics if `pow` is greater than `T::WIDTH`, or if any coefficient of `self` or `x` is
122 /// greater than or equal to $2^k$.
123 ///
124 /// # Examples
125 /// ```
126 /// use core::str::FromStr;
127 /// use malachite_base::polynomial::ModPowerOf2Evaluate;
128 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
129 ///
130 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
131 /// // 5 * 36 + 3 * 6 + 7 = 205, which is 13 mod 16.
132 /// assert_eq!(p.clone().mod_power_of_2_evaluate(6, 4), 13);
133 /// assert_eq!(p.clone().mod_power_of_2_evaluate(0, 4), 7);
134 /// // All 8 bits of a u8: 205 itself.
135 /// assert_eq!(p.mod_power_of_2_evaluate(6, 8), 205);
136 /// ```
137 ///
138 /// This is equivalent to `nmod_poly_evaluate_nmod` from `nmod_poly/evaluate_nmod.c`, FLINT
139 /// 3.6.0, with the modulus $2^k$, except that the value must be reduced.
140 #[inline]
141 fn mod_power_of_2_evaluate(self, x: T, pow: u64) -> T {
142 mod_power_of_2_evaluate(&self, x, pow)
143 }
144}
145
146// The shortest polynomial evaluated with Shoup's method, when `T` is wider than 32 bits: Shoup's
147// precomputation is a two-by-one division, which is only paid back from this length on. For
148// narrower types every polynomial of length 2 or more uses it. Tuned on Apple M-series for `u64`
149// and `u128`; FLINT's `FLINT_MULMOD_SHOUP_THRESHOLD` is 10.
150const MOD_EVALUATE_SHOUP_THRESHOLD: usize = 3;
151
152// Evaluates a polynomial at `x` modulo `m` with Horner's rule, reducing after every step.
153// `coefficients` must be nonempty.
154//
155// This is equivalent to `_nmod_poly_evaluate_nmod_horner` from `nmod_poly/evaluate_nmod.c`, FLINT
156// 3.6.0.
157crate_test_fn! {mod_evaluate_horner<T: PrimitiveUnsigned>(coefficients: &[T], x: T, m: T) -> T {
158 let data = T::precompute_mod_mul_data(&m);
159 let (&last, rest) = coefficients.split_last().unwrap();
160 let mut value = last;
161 for &c in rest.iter().rev() {
162 value.mod_mul_precomputed_assign(x, m, &data);
163 value.mod_add_assign(c, m);
164 }
165 value
166}}
167
168// Evaluates a polynomial at `x` modulo `m` with Horner's rule, multiplying by `x` with Shoup's
169// method. `coefficients` must be nonempty, `x_precomp` must be `mod_mul_precompute_shoup(x, m)`,
170// and the top bit of `m` must be clear.
171//
172// This is equivalent to `_nmod_poly_evaluate_nmod_precomp` from `nmod_poly/evaluate_nmod.c`, FLINT
173// 3.6.0.
174crate_test_fn! {mod_evaluate_shoup<T: PrimitiveUnsigned>(
175 coefficients: &[T],
176 x: T,
177 x_precomp: T,
178 m: T,
179) -> T {
180 let (&last, rest) = coefficients.split_last().unwrap();
181 let mut value = last;
182 for &c in rest.iter().rev() {
183 value = mod_mul_shoup(x, value, x_precomp, m);
184 value.mod_add_assign(c, m);
185 }
186 value
187}}
188
189// Evaluates a polynomial at `x` modulo `m` like `mod_evaluate_shoup`, but reduces only partially:
190// the result is congruent to the polynomial's value and less than $3m - 1$. `coefficients` must be
191// nonempty, `x_precomp` must be `mod_mul_precompute_shoup(x, m)`, and `m` must be at most `T::MAX /
192// 3`, so that $3m - 1$ values fit.
193//
194// This is equivalent to `_nmod_poly_evaluate_nmod_precomp_lazy` from `nmod_poly/evaluate_nmod.c`,
195// FLINT 3.6.0.
196crate_test_fn! {mod_evaluate_shoup_lazy<T: PrimitiveUnsigned>(
197 coefficients: &[T],
198 x: T,
199 x_precomp: T,
200 m: T,
201) -> T {
202 let (&last, rest) = coefficients.split_last().unwrap();
203 let mut value = last;
204 for &c in rest.iter().rev() {
205 // value is x * value mod m, or that plus m
206 value = mod_mul_shoup_lazy(x, value, x_precomp, m);
207 // value is now less than 3m - 1, since c < m
208 value.wrapping_add_assign(c);
209 }
210 value
211}}
212
213// Evaluates the polynomial with the given coefficients at `x` modulo `m`. The coefficients and `x`
214// must be less than `m`; this is not checked. It is public, but hidden, because malachite-nz
215// evaluates an `IntegerPolynomial` modulo a word with it, after reducing the coefficients.
216//
217// This is equivalent to `_nmod_poly_evaluate_nmod` from `nmod_poly/evaluate_nmod.c`, FLINT 3.6.0,
218// except that rectangular splitting is not used.
219#[doc(hidden)]
220pub fn mod_evaluate_slice<T: PrimitiveUnsigned>(coefficients: &[T], x: T, m: T) -> T {
221 let len = coefficients.len();
222 if len == 0 {
223 return T::ZERO;
224 }
225 if len == 1 || x == T::ZERO {
226 return coefficients[0];
227 }
228 // Shoup's method needs the top bit of m clear
229 if m.get_highest_bit() || (T::WIDTH > u32::WIDTH && len < MOD_EVALUATE_SHOUP_THRESHOLD) {
230 return mod_evaluate_horner(coefficients, x, m);
231 }
232 let x_precomp = mod_mul_precompute_shoup(x, m);
233 // The lazy loop's values are less than 3m - 1, so it is used when those fit: when m <= (2^W +
234 // 1) / 3, which is T::MAX / 3 since every width W is even. FLINT calls this bound LAZY_MAX.
235 if m <= T::MAX / T::from(3u8) {
236 let mut value = mod_evaluate_shoup_lazy(coefficients, x, x_precomp, m);
237 // correct the excess
238 let two_m = m << 1;
239 if value >= two_m {
240 value -= two_m;
241 } else if value >= m {
242 value -= m;
243 }
244 value
245 } else {
246 mod_evaluate_shoup(coefficients, x, x_precomp, m)
247 }
248}
249
250fn mod_evaluate<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, x: T, m: T) -> T {
251 assert!(
252 p.mod_is_reduced(&m),
253 "self must be reduced mod m, but {p} has a coefficient >= {m}"
254 );
255 assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
256 mod_evaluate_slice(&p.coefficients, x, m)
257}
258
259impl<T: PrimitiveUnsigned> ModEvaluate<T> for &UnsignedPolynomial<T> {
260 type Output = T;
261
262 /// Evaluates an [`UnsignedPolynomial`] at a value of its coefficient type, modulo a value of
263 /// that type, taking the polynomial by reference. The coefficients and the value must already
264 /// be reduced modulo `m`.
265 ///
266 /// $$
267 /// f(p, x, m) = \sum_{i=0}^{n-1} c_i x^i \bmod m,
268 /// $$
269 ///
270 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
271 /// evaluates to 0 everywhere.
272 ///
273 /// The value is found with Horner's rule, reducing after every step. When the polynomial is
274 /// long enough and the top bit of `m` is clear, every multiplication is by the same `x`, so
275 /// Shoup's method is used: $\lfloor x 2^W / m \rfloor$, where $W$ is the width of `T`, is
276 /// computed once, and each product is then reduced with a multiplication in place of a
277 /// division. When $m \leq (2^W - 1) / 3$ the reductions are also lazy, leaving the value below
278 /// $3m - 1$ until the end.
279 ///
280 /// # Worst-case complexity
281 /// $T(n) = O(n)$
282 ///
283 /// $M(n) = O(1)$
284 ///
285 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
286 ///
287 /// # Panics
288 /// Panics if `m` is 0, or if any coefficient of `self` or `x` is greater than or equal to `m`.
289 ///
290 /// # Examples
291 /// ```
292 /// use core::str::FromStr;
293 /// use malachite_base::polynomial::ModEvaluate;
294 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
295 ///
296 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
297 /// // 5 * 36 + 3 * 6 + 7 = 205, which is 10 mod 13.
298 /// assert_eq!((&p).mod_evaluate(6, 13), 10);
299 /// assert_eq!((&p).mod_evaluate(0, 13), 7);
300 /// // 205 itself, modulo a larger modulus.
301 /// assert_eq!((&p).mod_evaluate(6, 211), 205);
302 /// ```
303 ///
304 /// This is equivalent to `nmod_poly_evaluate_nmod` from `nmod_poly/evaluate_nmod.c`, FLINT
305 /// 3.6.0, except that the value must be reduced.
306 #[inline]
307 fn mod_evaluate(self, x: T, m: T) -> T {
308 mod_evaluate(self, x, m)
309 }
310}
311
312impl<T: PrimitiveUnsigned> ModEvaluate<T> for UnsignedPolynomial<T> {
313 type Output = T;
314
315 /// Evaluates an [`UnsignedPolynomial`] at a value of its coefficient type, modulo a value of
316 /// that type, taking the polynomial by value. The coefficients and the value must already be
317 /// reduced modulo `m`.
318 ///
319 /// $$
320 /// f(p, x, m) = \sum_{i=0}^{n-1} c_i x^i \bmod m,
321 /// $$
322 ///
323 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
324 /// evaluates to 0 everywhere.
325 ///
326 /// The value is found with Horner's rule, reducing after every step. When the polynomial is
327 /// long enough and the top bit of `m` is clear, every multiplication is by the same `x`, so
328 /// Shoup's method is used: $\lfloor x 2^W / m \rfloor$, where $W$ is the width of `T`, is
329 /// computed once, and each product is then reduced with a multiplication in place of a
330 /// division. When $m \leq (2^W - 1) / 3$ the reductions are also lazy, leaving the value below
331 /// $3m - 1$ until the end.
332 ///
333 /// # Worst-case complexity
334 /// $T(n) = O(n)$
335 ///
336 /// $M(n) = O(1)$
337 ///
338 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
339 ///
340 /// # Panics
341 /// Panics if `m` is 0, or if any coefficient of `self` or `x` is greater than or equal to `m`.
342 ///
343 /// # Examples
344 /// ```
345 /// use core::str::FromStr;
346 /// use malachite_base::polynomial::ModEvaluate;
347 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
348 ///
349 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
350 /// // 5 * 36 + 3 * 6 + 7 = 205, which is 10 mod 13.
351 /// assert_eq!(p.clone().mod_evaluate(6, 13), 10);
352 /// assert_eq!(p.clone().mod_evaluate(0, 13), 7);
353 /// // 205 itself, modulo a larger modulus.
354 /// assert_eq!(p.mod_evaluate(6, 211), 205);
355 /// ```
356 ///
357 /// This is equivalent to `nmod_poly_evaluate_nmod` from `nmod_poly/evaluate_nmod.c`, FLINT
358 /// 3.6.0, except that the value must be reduced.
359 #[inline]
360 fn mod_evaluate(self, x: T, m: T) -> T {
361 mod_evaluate(&self, x, m)
362 }
363}
364
365// The numbers of points evaluated together by `mod_evaluate_many_in_place`, for types at most 32
366// bits wide and for wider types. Horner's rule is a chain of dependent multiplications, so running
367// several points through one pass over the coefficients keeps more multiplications in flight. Tuned
368// on Apple M-series: for `u64` a block of 8 is 3.4 to 4.8 times as fast as one point at a time at
369// length 256, and for `u32` a block of 4 is best.
370const MOD_EVALUATE_MANY_NARROW_BLOCK: usize = 4;
371const MOD_EVALUATE_MANY_WIDE_BLOCK: usize = 8;
372
373// Replaces each of the `N` points in `xs` with the polynomial's value there modulo `m`, with
374// Horner's rule for all of them in one pass over the coefficients. `coefficients` must be nonempty,
375// and the points and coefficients reduced.
376crate_test_fn! {mod_evaluate_horner_block<T: PrimitiveUnsigned, const N: usize>(
377 coefficients: &[T],
378 xs: &mut [T; N],
379 m: T,
380) {
381 let data = T::precompute_mod_mul_data(&m);
382 let (&last, rest) = coefficients.split_last().unwrap();
383 let points = *xs;
384 let mut values = [last; N];
385 for &c in rest.iter().rev() {
386 for (value, &x) in values.iter_mut().zip(points.iter()) {
387 value.mod_mul_precomputed_assign(x, m, &data);
388 value.mod_add_assign(c, m);
389 }
390 }
391 *xs = values;
392}}
393
394// Like `mod_evaluate_horner_block`, multiplying by each point with Shoup's method. The top bit of
395// `m` must be clear.
396crate_test_fn! {mod_evaluate_shoup_block<T: PrimitiveUnsigned, const N: usize>(
397 coefficients: &[T],
398 xs: &mut [T; N],
399 m: T,
400) {
401 let points = *xs;
402 let precomps = points.map(|x| mod_mul_precompute_shoup(x, m));
403 let (&last, rest) = coefficients.split_last().unwrap();
404 let mut values = [last; N];
405 for &c in rest.iter().rev() {
406 for ((value, &x), &x_precomp) in values.iter_mut().zip(points.iter()).zip(precomps.iter()) {
407 *value = mod_mul_shoup(x, *value, x_precomp, m);
408 value.mod_add_assign(c, m);
409 }
410 }
411 *xs = values;
412}}
413
414// Like `mod_evaluate_shoup_block`, with lazy reduction as in `mod_evaluate_shoup_lazy`. `m` must be
415// at most `T::MAX / 3`. The values are fully reduced at the end.
416crate_test_fn! {mod_evaluate_shoup_lazy_block<T: PrimitiveUnsigned, const N: usize>(
417 coefficients: &[T],
418 xs: &mut [T; N],
419 m: T,
420) {
421 let points = *xs;
422 let precomps = points.map(|x| mod_mul_precompute_shoup(x, m));
423 let (&last, rest) = coefficients.split_last().unwrap();
424 let mut values = [last; N];
425 for &c in rest.iter().rev() {
426 for ((value, &x), &x_precomp) in values.iter_mut().zip(points.iter()).zip(precomps.iter()) {
427 // value is x * value mod m, or that plus m, and then less than 3m - 1
428 *value = mod_mul_shoup_lazy(x, *value, x_precomp, m);
429 value.wrapping_add_assign(c);
430 }
431 }
432 let two_m = m << 1;
433 for value in &mut values {
434 if *value >= two_m {
435 *value -= two_m;
436 } else if *value >= m {
437 *value -= m;
438 }
439 }
440 *xs = values;
441}}
442
443// Replaces each point in `xs` with the polynomial's value there modulo `m`. The points and the
444// coefficients must be reduced; this is not checked. Blocks of points are evaluated together, with
445// the method `mod_evaluate_slice` would choose for one point. Evaluates the points in `xs` in
446// blocks of `N`, with the method chosen by `mod_evaluate_many_in_place`, and returns the leftover
447// points, fewer than `N` of them.
448fn mod_evaluate_many_blocks<'a, T: PrimitiveUnsigned, const N: usize>(
449 coefficients: &[T],
450 xs: &'a mut [T],
451 m: T,
452 shoup: bool,
453 lazy: bool,
454) -> &'a mut [T] {
455 let (blocks, remainder) = xs.as_chunks_mut::<N>();
456 for block in blocks {
457 if !shoup {
458 mod_evaluate_horner_block(coefficients, block, m);
459 } else if lazy {
460 mod_evaluate_shoup_lazy_block(coefficients, block, m);
461 } else {
462 mod_evaluate_shoup_block(coefficients, block, m);
463 }
464 }
465 remainder
466}
467
468// Replaces each point in `xs` with the polynomial's value there modulo `m`. The points and the
469// coefficients must be reduced; this is not checked. Blocks of points are evaluated together, with
470// the method `mod_evaluate_slice` would choose for one point.
471crate_test_fn! {mod_evaluate_many_in_place<T: PrimitiveUnsigned>(
472 coefficients: &[T],
473 xs: &mut [T],
474 m: T,
475) {
476 let len = coefficients.len();
477 match len {
478 0 => xs.fill(T::ZERO),
479 1 => xs.fill(coefficients[0]),
480 _ => {
481 // As in mod_evaluate_slice: Shoup's method needs the top bit of m clear, and is used
482 // for short polynomials only when T is at most 32 bits wide
483 let shoup = !m.get_highest_bit()
484 && (T::WIDTH <= u32::WIDTH || len >= MOD_EVALUATE_SHOUP_THRESHOLD);
485 let lazy = m <= T::MAX / T::from(3u8);
486 // Wider types take blocks of the wide size first; the leftover points go through blocks
487 // of the narrow size, and then one at a time
488 let xs = if T::WIDTH <= u32::WIDTH {
489 xs
490 } else {
491 mod_evaluate_many_blocks::<T, MOD_EVALUATE_MANY_WIDE_BLOCK>(
492 coefficients,
493 xs,
494 m,
495 shoup,
496 lazy,
497 )
498 };
499 let remainder = mod_evaluate_many_blocks::<T, MOD_EVALUATE_MANY_NARROW_BLOCK>(
500 coefficients,
501 xs,
502 m,
503 shoup,
504 lazy,
505 );
506 for x in remainder {
507 *x = mod_evaluate_slice(coefficients, *x, m);
508 }
509 }
510 }
511}}
512
513impl<T: PrimitiveUnsigned> ModEvaluateMany<T> for &UnsignedPolynomial<T> {
514 type Output = T;
515
516 /// Evaluates an [`UnsignedPolynomial`] at each of several values of its coefficient type,
517 /// modulo a value of that type. The coefficients and the values must already be reduced modulo
518 /// `m`.
519 ///
520 /// $$
521 /// f(p, (x_j)_{j=0}^{k-1}, m) = \left ( \sum_{i=0}^{n-1} c_i x_j^i \bmod m
522 /// \right )_{j=0}^{k-1},
523 /// $$
524 ///
525 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length.
526 ///
527 /// The result is the same as calling
528 /// [`mod_evaluate`](crate::polynomial::ModEvaluate::mod_evaluate) at each value, with the same
529 /// choice between Horner's rule and Shoup's method, but the polynomial is checked once, and
530 /// several values are evaluated together in each pass over the coefficients. Horner's rule is a
531 /// chain of dependent multiplications, so interleaving independent chains keeps the processor's
532 /// multipliers busy.
533 ///
534 /// # Worst-case complexity
535 /// $T(n, k) = O(nk)$
536 ///
537 /// $M(k) = O(k)$
538 ///
539 /// where $T$ is time, $M$ is additional memory, $n$ is `self.len()`, and $k$ is `xs.len()`.
540 ///
541 /// # Panics
542 /// Panics if `m` is 0, or if any coefficient of `self` or any value in `xs` is greater than or
543 /// equal to `m`.
544 ///
545 /// # Examples
546 /// ```
547 /// use core::str::FromStr;
548 /// use malachite_base::polynomial::ModEvaluateMany;
549 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
550 ///
551 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
552 /// // 7, 15, 33, 61, 99, and 147, mod 13
553 /// assert_eq!(
554 /// (&p).mod_evaluate_many(&[0, 1, 2, 3, 4, 5], 13),
555 /// &[7, 2, 7, 9, 8, 4]
556 /// );
557 /// ```
558 ///
559 /// This is equivalent to `nmod_poly_evaluate_nmod_vec_iter` from
560 /// `nmod_poly/evaluate_nmod_vec.c`, FLINT 3.6.0, except that the values must be reduced.
561 fn mod_evaluate_many(self, xs: &[T], m: T) -> Vec<T> {
562 assert!(
563 self.mod_is_reduced(&m),
564 "self must be reduced mod m, but {self} has a coefficient >= {m}"
565 );
566 for &x in xs {
567 assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
568 }
569 let mut values = xs.to_vec();
570 mod_evaluate_many_in_place(&self.coefficients, &mut values, m);
571 values
572 }
573}
574
575// Whether geometric evaluation of a polynomial of length `n` at `k` points modulo `m` uses
576// Bluestein's trick rather than evaluating at each power of q: when the polynomial and the number
577// of points are both long enough. Evaluating at each power is fast with Shoup's multiplication, and
578// fastest with its lazy form, which needs the top two bits of `m` clear; when the top bit is set,
579// Shoup's multiplication is unavailable and Bluestein's trick wins much sooner. The middle product
580// accumulates in three words once `m` has more than about W - 8 bits, where W is `T::WIDTH`, which
581// slows it. Measured on an Apple M-series machine, 2026-10, for `u64` with moduli of 20, 30, 40,
582// 50, 62, 63, and 64 bits; the boundaries for other types are scaled by their width without being
583// measured.
584fn mod_evaluate_geometric_fast_preferred<T: PrimitiveUnsigned>(n: usize, k: usize, m: T) -> bool {
585 let bits = m.significant_bits();
586 let width = T::WIDTH;
587 let (min_n, min_k) = if bits == width {
588 (32, 32)
589 } else if bits == width - 1 {
590 (256, 256)
591 } else if bits + 8 > width {
592 (512, 1024)
593 } else if bits << 4 > width * 5 {
594 (256, 256)
595 } else {
596 (128, 128)
597 };
598 n >= min_n && k >= min_k
599}
600
601// Evaluates the polynomial with the given coefficients, reduced modulo `m`, at $1, q, q^2, \ldots,
602// q^{k-1}$ modulo `m`, generating the powers of q and then evaluating at them all, several points
603// at a time.
604//
605// This is equivalent to `_nmod_poly_evaluate_geometric_nmod_vec_iter` from
606// `nmod_poly/evaluate_geometric_nmod_vec.c`, FLINT 3.6.0, with the ratio q in place of FLINT's
607// $r^2$.
608crate_test_fn! {mod_evaluate_geometric_iter<T: PrimitiveUnsigned>(
609 coefficients: &[T],
610 q: T,
611 k: usize,
612 m: T,
613) -> Vec<T> {
614 let mut values = Vec::with_capacity(k);
615 if k != 0 {
616 let mut power = T::ONE % m;
617 values.push(power);
618 if m.get_highest_bit() {
619 let data = T::precompute_mod_mul_data(&m);
620 for _ in 1..k {
621 power.mod_mul_precomputed_assign(q, m, &data);
622 values.push(power);
623 }
624 } else {
625 let q_precomp = mod_mul_precompute_shoup(q, m);
626 for _ in 1..k {
627 power = mod_mul_shoup(q, power, q_precomp, m);
628 values.push(power);
629 }
630 }
631 }
632 mod_evaluate_many_in_place(coefficients, &mut values, m);
633 values
634}}
635
636// Evaluates the polynomial with the given coefficients, reduced modulo `m`, at $1, q, q^2, \ldots,
637// q^{k-1}$ modulo `m` with Bluestein's trick, where `q_inverse` is the inverse of q modulo `m`.
638//
639// With $\binom{t}{2} = t(t-1)/2$, every product $ij$ is $\binom{i+j}{2} - \binom{i}{2} -
640// \binom{j}{2}$, so
641// $$
642// \sum_i c_i q^{ij} = q^{-\binom{j}{2}} \sum_i \left ( c_i q^{-\binom{i}{2}} \right )
643// q^{\binom{i+j}{2}}.
644// $$
645// The sum is coefficient $n - 1 + j$ of the product of the reverse of $(c_i q^{-\binom{i}{2}})_i$
646// and $(q^{\binom{t}{2}})_t$, so all $k$ values are one middle product, the $k$ coefficients of
647// that product from coefficient $n - 1$ on. Leading zero coefficients of the polynomial are
648// skipped, shortening $n$.
649//
650// This is equivalent to `_nmod_poly_evaluate_geometric_nmod_vec_fast` from
651// `nmod_poly/evaluate_geometric_nmod_vec.c`, FLINT 3.6.0, with exponents $\binom{t}{2}$ in place of
652// FLINT's $t^2/2$, so that no square root of q is needed.
653crate_test_fn! {mod_evaluate_geometric_fast<T: PrimitiveUnsigned>(
654 coefficients: &[T],
655 q: T,
656 q_inverse: T,
657 k: usize,
658 m: T,
659) -> Vec<T> {
660 if k == 0 {
661 return Vec::new();
662 }
663 let Some(start) = coefficients.iter().position(|&c| c != T::ZERO) else {
664 return vec![T::ZERO; k];
665 };
666 let n = coefficients.len();
667 let a_len = n - start;
668 let data = T::precompute_mod_mul_data(&m);
669 // binomial_powers[t] = q^C(t, 2), for t < n + k - 1.
670 let b_len = n + k - 1;
671 let mut binomial_powers = Vec::with_capacity(b_len);
672 let mut power = T::ONE % m;
673 let mut step = T::ONE % m;
674 for _ in 0..b_len {
675 binomial_powers.push(power);
676 power.mod_mul_precomputed_assign(step, m, &data);
677 step.mod_mul_precomputed_assign(q, m, &data);
678 }
679 // inverse_powers[t] = q^-C(t, 2), for t < max(n, k).
680 let w_len = max(n, k);
681 let mut inverse_powers = Vec::with_capacity(w_len);
682 let mut power = T::ONE % m;
683 let mut step = T::ONE % m;
684 for _ in 0..w_len {
685 inverse_powers.push(power);
686 power.mod_mul_precomputed_assign(step, m, &data);
687 step.mod_mul_precomputed_assign(q_inverse, m, &data);
688 }
689 // The scaled coefficients, from the first nonzero one on, reversed.
690 let scaled: Vec<T> = coefficients[start..]
691 .iter()
692 .zip(&inverse_powers[start..])
693 .rev()
694 .map(|(&c, &w)| c.mod_mul_precomputed(w, m, &data))
695 .collect();
696 let mut product = vec![T::ZERO; k];
697 mod_mul_middle_karatsuba(
698 &mut product,
699 &scaled,
700 &binomial_powers[start..start + a_len + k - 1],
701 &ModData::new(m, a_len),
702 );
703 product
704 .iter()
705 .zip(&inverse_powers)
706 .map(|(&z, &w)| z.mod_mul_precomputed(w, m, &data))
707 .collect()
708}}
709
710// Evaluates like `mod_evaluate_geometric_fast`, but computes the sums with a truncated product of
711// length $n + k - 1$ and keeps its last $k$ coefficients, discarding the first $n - 1$, rather than
712// with a middle product. It is kept to measure what the middle product saves.
713crate_test_fn! {
714#[allow(dead_code)]
715mod_evaluate_geometric_fast_truncated<T: PrimitiveUnsigned>(
716 coefficients: &[T],
717 q: T,
718 q_inverse: T,
719 k: usize,
720 m: T,
721) -> Vec<T> {
722 if k == 0 {
723 return Vec::new();
724 }
725 let Some(start) = coefficients.iter().position(|&c| c != T::ZERO) else {
726 return vec![T::ZERO; k];
727 };
728 let n = coefficients.len();
729 let a_len = n - start;
730 let data = T::precompute_mod_mul_data(&m);
731 // binomial_powers[t] = q^C(t, 2), for t < n + k - 1.
732 let b_len = n + k - 1;
733 let mut binomial_powers = Vec::with_capacity(b_len);
734 let mut power = T::ONE % m;
735 let mut step = T::ONE % m;
736 for _ in 0..b_len {
737 binomial_powers.push(power);
738 power.mod_mul_precomputed_assign(step, m, &data);
739 step.mod_mul_precomputed_assign(q, m, &data);
740 }
741 // inverse_powers[t] = q^-C(t, 2), for t < max(n, k).
742 let w_len = max(n, k);
743 let mut inverse_powers = Vec::with_capacity(w_len);
744 let mut power = T::ONE % m;
745 let mut step = T::ONE % m;
746 for _ in 0..w_len {
747 inverse_powers.push(power);
748 power.mod_mul_precomputed_assign(step, m, &data);
749 step.mod_mul_precomputed_assign(q_inverse, m, &data);
750 }
751 // The scaled coefficients, from the first nonzero one on, reversed.
752 let scaled: Vec<T> = coefficients[start..]
753 .iter()
754 .zip(&inverse_powers[start..])
755 .rev()
756 .map(|(&c, &w)| c.mod_mul_precomputed(w, m, &data))
757 .collect();
758 let mut product = vec![T::ZERO; a_len + k - 1];
759 mod_mul_truncated_to_out(
760 &mut product,
761 &scaled,
762 &binomial_powers[start..start + a_len + k - 1],
763 m,
764 );
765 product[a_len - 1..]
766 .iter()
767 .zip(&inverse_powers)
768 .map(|(&z, &w)| z.mod_mul_precomputed(w, m, &data))
769 .collect()
770}}
771
772impl<T: PrimitiveUnsigned> ModEvaluateGeometric<T> for &UnsignedPolynomial<T> {
773 type Output = T;
774
775 /// Evaluates an [`UnsignedPolynomial`] at $1, q, q^2, \ldots, q^{k-1}$, modulo a value of its
776 /// coefficient type. The coefficients and `q` must already be reduced modulo `m`.
777 ///
778 /// $$
779 /// f(p, q, k, m) = \left ( \sum_{i=0}^{n-1} c_i q^{ij} \bmod m \right )_{j=0}^{k-1},
780 /// $$
781 ///
782 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length.
783 ///
784 /// The powers of `q` are computed with Shoup's method when the top bit of `m` is clear, since
785 /// every multiplication is by `q`, and are then evaluated as by
786 /// [`mod_evaluate_many`](crate::polynomial::ModEvaluateMany::mod_evaluate_many), in place.
787 ///
788 /// # Worst-case complexity
789 /// $T(n, k) = O(nk)$
790 ///
791 /// $M(k) = O(k)$
792 ///
793 /// where $T$ is time, $M$ is additional memory, $n$ is `self.len()`, and $k$ is `k`.
794 ///
795 /// # Panics
796 /// Panics if `m` is 0, or if any coefficient of `self` or `q` is greater than or equal to `m`.
797 ///
798 /// # Examples
799 /// ```
800 /// use core::str::FromStr;
801 /// use malachite_base::polynomial::ModEvaluateGeometric;
802 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
803 ///
804 /// let p = UnsignedPolynomial::<u8>::from_str("5*x^2+3*x+7").unwrap();
805 /// // At 1, 2, 4, and 8: 15, 33, 99, and 351, mod 13
806 /// assert_eq!((&p).mod_evaluate_geometric(2, 4, 13), &[2, 7, 8, 0]);
807 /// ```
808 ///
809 /// This is equivalent to `nmod_poly_evaluate_geometric_nmod_vec_iter` from
810 /// `nmod_poly/evaluate_geometric_nmod_vec.c`, FLINT 3.6.0, with `q` in place of FLINT's $r^2$:
811 /// FLINT evaluates at the powers of the square of its argument.
812 fn mod_evaluate_geometric(self, q: T, k: u64, m: T) -> Vec<T> {
813 assert!(
814 self.mod_is_reduced(&m),
815 "self must be reduced mod m, but {self} has a coefficient >= {m}"
816 );
817 assert!(q < m, "q must be reduced mod m, but {q} >= {m}");
818 let k = usize::exact_from(k);
819 let n = self.coefficients.len();
820 if mod_evaluate_geometric_fast_preferred(n, k, m)
821 && q != T::ZERO
822 && let Some(q_inverse) = q.mod_inverse(m)
823 {
824 mod_evaluate_geometric_fast(&self.coefficients, q, q_inverse, k, m)
825 } else {
826 mod_evaluate_geometric_iter(&self.coefficients, q, k, m)
827 }
828 }
829}