malachite_nz/integer_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::integer::Integer;
10use crate::integer_polynomial::IntegerPolynomial;
11use crate::natural::Natural;
12use crate::platform::Limb;
13use alloc::vec::Vec;
14use core::iter::Sum;
15use core::mem::replace;
16use core::ops::{AddAssign, Mul, MulAssign};
17use malachite_base::num::arithmetic::traits::{Parity, PowerOf2, Square};
18use malachite_base::num::basic::integers::PrimitiveInt;
19use malachite_base::num::basic::traits::{One, Zero};
20use malachite_base::num::conversion::traits::ExactFrom;
21use malachite_base::num::logic::traits::{BitAccess, SignificantBits};
22use malachite_base::polynomial::{Evaluate, EvaluateMany, ModEvaluate};
23use malachite_base::unsigned_polynomial::arithmetic::evaluate::mod_evaluate_slice;
24
25// Evaluates a polynomial, given by its coefficients in ascending order, at `x` with Horner's rule.
26//
27// This is equivalent to `_fmpz_poly_evaluate_horner_fmpz` from `fmpz_poly/evaluate_horner_fmpz.c`,
28// FLINT 3.6.0.
29crate_test_fn! {evaluate_horner<T>(coefficients: &[T], x: &T) -> T
30where
31 T: Clone + Zero + PartialEq + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
32{
33 let Some((leading, rest)) = coefficients.split_last() else {
34 return T::ZERO;
35 };
36 if rest.is_empty() || *x == T::ZERO {
37 return coefficients[0].clone();
38 }
39 let mut value = leading.clone();
40 for c in rest.iter().rev() {
41 value *= x;
42 value += c;
43 }
44 value
45}}
46
47// The block structure shared by the divide-and-conquer evaluations, at an integer here and at a
48// rational in malachite-q.
49//
50// Adjacent coefficients are paired into blocks, and blocks are merged like the carries of a binary
51// counter: a block covering `2^k` coefficients waits in `partials[k]` until an equal one follows
52// it, and the two are merged. Merging equal halves keeps the operands of each multiplication about
53// the same size, where Horner's rule multiplies an ever larger accumulator by the same value.
54// Whatever blocks remain at the end are merged from the smallest up.
55//
56// `pair(c_i, c_{i+1})` makes the block of two adjacent coefficients, and `single(c)` the block of
57// the last coefficient when the length is odd. `merge(lower, upper, k, upper_len)` combines a block
58// `lower` of `2^k` coefficients with the block `upper` of `upper_len` coefficients directly above
59// it. There must be at least 2 coefficients.
60//
61// This is the loop structure of `_fmpz_poly_evaluate_divconquer_fmpz` from
62// `fmpz_poly/evaluate_divconquer_fmpz.c`, FLINT 3.6.0, with the arithmetic left to the caller.
63#[doc(hidden)]
64pub fn divide_and_conquer_blocks<C, T: Clone + Zero>(
65 coefficients: &[C],
66 pair: impl Fn(&C, &C) -> T,
67 single: impl Fn(&C) -> T,
68 merge: impl Fn(&T, T, usize, usize) -> T,
69) -> T {
70 let len = coefficients.len();
71 assert!(len >= 2);
72 // 2^{h - 1} < len <= 2^h, and h >= 1.
73 let h = usize::exact_from((len - 1).significant_bits());
74 let mut partials = vec![T::ZERO; h + 1];
75 // Absorbs a block of `block_len` coefficients ending just before coefficient `end` into the
76 // pending blocks, merging it with as many of them as the carries of `end` call for, and returns
77 // where it was left and how many coefficients it then covers.
78 let absorb =
79 |mut block: T, mut block_len: usize, end: usize, partials: &mut [T]| -> (usize, usize) {
80 let carries = usize::exact_from(end.trailing_zeros());
81 let mut k = 1;
82 while k < carries {
83 block = merge(&partials[k], block, k, block_len);
84 block_len += usize::power_of_2(u64::exact_from(k));
85 k += 1;
86 }
87 partials[k] = block;
88 (k, block_len)
89 };
90 let mut k = 1;
91 let mut top_len = 0;
92 for (i, [low, high]) in coefficients.as_chunks::<2>().0.iter().enumerate() {
93 (k, top_len) = absorb(pair(low, high), 2, (i + 1) << 1, &mut partials);
94 }
95 if len.odd() {
96 (k, top_len) = absorb(single(&coefficients[len - 1]), 1, len + 1, &mut partials);
97 }
98 let mut value = replace(&mut partials[k], T::ZERO);
99 while k < h {
100 if (len - 1).get_bit(u64::exact_from(k)) {
101 value = merge(&partials[k], value, k, top_len);
102 top_len += usize::power_of_2(u64::exact_from(k));
103 }
104 k += 1;
105 }
106 value
107}
108
109// Evaluates a polynomial, given by its coefficients in ascending order, at `x` by divide and
110// conquer: a block of coefficients `c_i, ..., c_{i + l - 1}` has the value `c_i + c_{i + 1} x + ...
111// + c_{i + l - 1} x^{l - 1}`, and a block of `2^k` coefficients below one with value `v` merges
112// with it as `lower + x^{2^k} v`.
113//
114// This is equivalent to `fmpz_poly_evaluate_divconquer_fmpz` and
115// `_fmpz_poly_evaluate_divconquer_fmpz` from `fmpz_poly/evaluate_divconquer_fmpz.c`, FLINT 3.6.0.
116crate_test_fn! {evaluate_divide_and_conquer<T>(coefficients: &[T], x: &T) -> T
117where
118 T: Clone + Zero + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
119 for<'a> &'a T: Mul<&'a T, Output = T> + Square<Output = T>,
120{
121 match coefficients.len() {
122 0 => return T::ZERO,
123 1 => return coefficients[0].clone(),
124 _ => {}
125 }
126 let h = usize::exact_from((coefficients.len() - 1).significant_bits());
127 // powers[k - 1] is x^{2^k}, for 1 <= k < h; x itself is borrowed rather than stored.
128 let mut powers: Vec<T> = Vec::with_capacity(h - 1);
129 for k in 1..h {
130 let square = if k == 1 {
131 x.square()
132 } else {
133 powers[k - 2].square()
134 };
135 powers.push(square);
136 }
137 let power = |k: usize| if k == 0 { x } else { &powers[k - 1] };
138 divide_and_conquer_blocks(
139 coefficients,
140 |low, high| {
141 let mut block = high * x;
142 block += low;
143 block
144 },
145 T::clone,
146 |lower, mut upper, k, _| {
147 upper *= power(k);
148 upper += lower;
149 upper
150 },
151 )
152}}
153
154// Divide and conquer only pays once its balanced multiplications are large enough for a
155// subquadratic multiplication algorithm, and when does that depends on the size of `x` as much as
156// on the number of coefficients. A one-limb `x` makes every step of Horner's rule a cheap
157// multiplication by one limb, and divide and conquer takes a long polynomial to catch up; otherwise
158// the crossover follows the product of the length and the number of limbs of `x`. The size of the
159// coefficients moves the crossover too, but both ways, and not enough to be worth a scan of them.
160//
161// Tuned on 64-bit Apple Silicon with the `evaluate` tuning level (`-g tune_evaluate`).
162pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_ONE_LIMB_THRESHOLD: usize = 1024;
163pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_LENGTH_TIMES_LIMBS_THRESHOLD: usize = 256;
164pub(crate) const EVALUATE_DIVIDE_AND_CONQUER_MIN_LENGTH: usize = 4;
165
166// Whether divide and conquer beats Horner's rule for a polynomial with `len` coefficients at a
167// value of `x_bits` bits.
168pub(crate) fn evaluate_use_divide_and_conquer(len: usize, x_bits: u64) -> bool {
169 let x_limbs = usize::exact_from(x_bits.div_ceil(Limb::WIDTH));
170 if x_limbs <= 1 {
171 len >= EVALUATE_DIVIDE_AND_CONQUER_ONE_LIMB_THRESHOLD
172 } else {
173 len >= EVALUATE_DIVIDE_AND_CONQUER_MIN_LENGTH
174 && len.saturating_mul(x_limbs)
175 >= EVALUATE_DIVIDE_AND_CONQUER_LENGTH_TIMES_LIMBS_THRESHOLD
176 }
177}
178
179// Evaluates a polynomial, given by its coefficients in ascending order, at `x`, choosing between
180// Horner's rule and divide and conquer by the length and the size of `x`. At `x = 1` the value is
181// the sum of the coefficients, which needs no multiplication at all.
182//
183// This is equivalent to `_fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT 3.6.0,
184// except for the shortcut at 1 and the choice of algorithm, where FLINT switches at 50 coefficients
185// whatever the size of `x`.
186pub(crate) fn evaluate<T>(coefficients: &[T], x: &T) -> T
187where
188 T: Clone + Zero + One + PartialEq + for<'a> AddAssign<&'a T> + for<'a> MulAssign<&'a T>,
189 T: for<'a> Sum<&'a T>,
190 for<'a> &'a T: Mul<&'a T, Output = T> + Square<Output = T> + SignificantBits,
191{
192 if *x == T::ONE {
193 coefficients.iter().sum()
194 } else if evaluate_use_divide_and_conquer(coefficients.len(), x.significant_bits()) {
195 evaluate_divide_and_conquer(coefficients, x)
196 } else {
197 evaluate_horner(coefficients, x)
198 }
199}
200
201// At `x = -1` the value is the alternating sum of the coefficients, which needs no multiplication.
202fn evaluate_integer(coefficients: &[Integer], x: &Integer) -> Integer {
203 if *x == -1i32 {
204 coefficients.iter().step_by(2).sum::<Integer>()
205 - coefficients.iter().skip(1).step_by(2).sum::<Integer>()
206 } else {
207 evaluate(coefficients, x)
208 }
209}
210
211impl Evaluate<&Integer> for &IntegerPolynomial {
212 type Output = Integer;
213
214 /// Evaluates an [`IntegerPolynomial`] at an [`Integer`], taking both by reference.
215 ///
216 /// $$
217 /// f(p, x) = \sum_{i=0}^{n-1} c_i x^i,
218 /// $$
219 ///
220 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
221 /// evaluates to 0 everywhere.
222 ///
223 /// Horner's rule is used unless the polynomial is long compared with the size of `x`, in which
224 /// case divide and conquer, which pairs off coefficients and merges the pairs so that each
225 /// multiplication has operands of about the same size, is faster.
226 ///
227 /// # Worst-case complexity
228 /// $T(n) = O(n \log^2 n \log\log n)$
229 ///
230 /// $M(n) = O(n \log n)$
231 ///
232 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()` times the larger of the
233 /// greatest number of bits of any coefficient and the number of bits of `x`.
234 ///
235 /// # Examples
236 /// ```
237 /// use core::str::FromStr;
238 /// use malachite_base::num::basic::traits::{One, Two, Zero};
239 /// use malachite_base::polynomial::Evaluate;
240 /// use malachite_nz::integer::Integer;
241 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
242 ///
243 /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
244 /// assert_eq!((&p).evaluate(&Integer::ZERO), 2);
245 /// assert_eq!((&p).evaluate(&Integer::ONE), 0);
246 /// assert_eq!((&p).evaluate(&Integer::from(-2)), 12);
247 /// assert_eq!((&p).evaluate(&Integer::from(10)), 72);
248 ///
249 /// let q = IntegerPolynomial::from_str("x^100-1").unwrap();
250 /// assert_eq!(
251 /// (&q).evaluate(&Integer::TWO).to_string(),
252 /// "1267650600228229401496703205375"
253 /// );
254 /// ```
255 ///
256 /// This is equivalent to `fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT
257 /// 3.6.0.
258 #[inline]
259 fn evaluate(self, x: &Integer) -> Integer {
260 evaluate_integer(&self.coefficients, x)
261 }
262}
263
264impl Evaluate<Integer> for &IntegerPolynomial {
265 type Output = Integer;
266
267 /// Evaluates an [`IntegerPolynomial`] at an [`Integer`], taking the polynomial by reference and
268 /// the value by value.
269 ///
270 /// $$
271 /// f(p, x) = \sum_{i=0}^{n-1} c_i x^i,
272 /// $$
273 ///
274 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length. The zero polynomial
275 /// evaluates to 0 everywhere.
276 ///
277 /// Horner's rule is used unless the polynomial is long compared with the size of `x`, in which
278 /// case divide and conquer, which pairs off coefficients and merges the pairs so that each
279 /// multiplication has operands of about the same size, is faster.
280 ///
281 /// # Worst-case complexity
282 /// $T(n) = O(n \log^2 n \log\log n)$
283 ///
284 /// $M(n) = O(n \log n)$
285 ///
286 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()` times the larger of the
287 /// greatest number of bits of any coefficient and the number of bits of `x`.
288 ///
289 /// # Examples
290 /// ```
291 /// use core::str::FromStr;
292 /// use malachite_base::num::basic::traits::{One, Two, Zero};
293 /// use malachite_base::polynomial::Evaluate;
294 /// use malachite_nz::integer::Integer;
295 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
296 ///
297 /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
298 /// assert_eq!((&p).evaluate(Integer::ZERO), 2);
299 /// assert_eq!((&p).evaluate(Integer::ONE), 0);
300 /// assert_eq!((&p).evaluate(Integer::from(-2)), 12);
301 /// assert_eq!((&p).evaluate(Integer::from(10)), 72);
302 ///
303 /// let q = IntegerPolynomial::from_str("x^100-1").unwrap();
304 /// assert_eq!(
305 /// (&q).evaluate(Integer::TWO).to_string(),
306 /// "1267650600228229401496703205375"
307 /// );
308 /// ```
309 ///
310 /// This is equivalent to `fmpz_poly_evaluate_fmpz` from `fmpz_poly/evaluate_fmpz.c`, FLINT
311 /// 3.6.0.
312 #[inline]
313 fn evaluate(self, x: Integer) -> Integer {
314 evaluate_integer(&self.coefficients, &x)
315 }
316}
317
318// Returns `c` modulo `m`, in $[0, m)$, where `m_natural` is `m` as a `Natural`.
319fn integer_mod_u64(c: &Integer, m: u64, m_natural: &Natural) -> u64 {
320 let r = u64::exact_from(&(&c.abs % m_natural));
321 if c.sign || r == 0 { r } else { m - r }
322}
323
324impl ModEvaluate<u64> for &IntegerPolynomial {
325 type Output = u64;
326
327 /// Evaluates an [`IntegerPolynomial`] at a [`u64`], modulo a [`u64`]. The coefficients may be
328 /// any [`Integer`]s, and are reduced as the evaluation goes; `x` must already be reduced modulo
329 /// `m`.
330 ///
331 /// $$
332 /// f(p, x, m) = \left ( \sum_{i=0}^{n-1} c_i x^i \right ) \bmod m,
333 /// $$
334 ///
335 /// where $c_i$ is the coefficient of $x^i$ in $p$, $n$ is its length, and the result is in $[0,
336 /// m)$. The zero polynomial evaluates to 0 everywhere.
337 ///
338 /// Each coefficient is reduced to a word, and the words are then evaluated as by
339 /// [`UnsignedPolynomial::mod_evaluate`](malachite_base::polynomial::ModEvaluate::mod_evaluate),
340 /// with Horner's rule and, for longer polynomials, Shoup's method.
341 ///
342 /// # Worst-case complexity
343 /// $T(n, m) = O(n + m)$
344 ///
345 /// $M(m) = O(m)$
346 ///
347 /// where $T$ is time, $M$ is additional memory, $n$ is the total number of bits of the
348 /// coefficients, and $m$ is `self.len()`.
349 ///
350 /// # Panics
351 /// Panics if `m` is 0, or if `x` is greater than or equal to `m`.
352 ///
353 /// # Examples
354 /// ```
355 /// use core::str::FromStr;
356 /// use malachite_base::polynomial::ModEvaluate;
357 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
358 ///
359 /// let p = IntegerPolynomial::from_str("-5*x^2+3*x-7").unwrap();
360 /// // -5 * 36 + 3 * 6 - 7 = -169, which is 7 mod 11.
361 /// assert_eq!((&p).mod_evaluate(6, 11), 7);
362 /// // The coefficients need not be reduced.
363 /// let p = IntegerPolynomial::from_str("100*x+1").unwrap();
364 /// assert_eq!((&p).mod_evaluate(3, 10), 1);
365 /// ```
366 ///
367 /// This is equivalent to `fmpz_poly_evaluate_mod` from `fmpz_poly/mod_evaluate.c`, FLINT 3.6.0,
368 /// except that `x` must be reduced.
369 fn mod_evaluate(self, x: u64, m: u64) -> u64 {
370 assert_ne!(m, 0, "m cannot be 0");
371 assert!(x < m, "x must be reduced mod m, but {x} >= {m}");
372 let m_natural = Natural::from(m);
373 match self.coefficients.as_slice() {
374 [] => 0,
375 [c, ..] if x == 0 => integer_mod_u64(c, m, &m_natural),
376 coefficients => {
377 let reduced: Vec<u64> = coefficients
378 .iter()
379 .map(|c| integer_mod_u64(c, m, &m_natural))
380 .collect();
381 mod_evaluate_slice(&reduced, x, m)
382 }
383 }
384 }
385}
386
387impl EvaluateMany<Integer> for &IntegerPolynomial {
388 type Output = Integer;
389
390 /// Evaluates an [`IntegerPolynomial`] at each of several [`Integer`]s.
391 ///
392 /// $$
393 /// f(p, (x_j)_{j=0}^{k-1}) = \left ( \sum_{i=0}^{n-1} c_i x_j^i \right )_{j=0}^{k-1},
394 /// $$
395 ///
396 /// where $c_i$ is the coefficient of $x^i$ in $p$ and $n$ is its length.
397 ///
398 /// Each value is found as by [`evaluate`](malachite_base::polynomial::Evaluate::evaluate),
399 /// which chooses between Horner's rule and divide and conquer by the length of the polynomial
400 /// and the size of the value.
401 ///
402 /// # Worst-case complexity
403 /// $T(n, k) = O(kn \log^2 n \log\log n)$
404 ///
405 /// $M(n, k) = O(kn \log n)$
406 ///
407 /// where $T$ is time, $M$ is additional memory, $k$ is `xs.len()`, and $n$ is `self.len()`
408 /// times the larger of the greatest number of bits of any coefficient and the greatest number
409 /// of bits of any value in `xs`.
410 ///
411 /// # Examples
412 /// ```
413 /// use core::str::FromStr;
414 /// use malachite_base::polynomial::EvaluateMany;
415 /// use malachite_nz::integer::Integer;
416 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
417 ///
418 /// let p = IntegerPolynomial::from_str("x^2-3*x+2").unwrap();
419 /// let xs = [-1i32, 0, 1, 2, 3].map(Integer::from);
420 /// assert_eq!(
421 /// (&p).evaluate_many(&xs),
422 /// [6i32, 2, 0, 0, 2].map(Integer::from)
423 /// );
424 /// ```
425 ///
426 /// This is equivalent to `fmpz_poly_evaluate_fmpz_vec` from `fmpz_poly/evaluate_fmpz_vec.c`,
427 /// FLINT 3.6.0.
428 #[inline]
429 fn evaluate_many(self, xs: &[Integer]) -> Vec<Integer> {
430 xs.iter().map(|x| self.evaluate(x)).collect()
431 }
432}