malachite_nz/integer_polynomial/arithmetic/pow/mod.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the FLINT Library.
4//
5// Copyright © 2010 Sebastian Pancratz
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::integer_polynomial::IntegerPolynomial;
14use crate::integer_polynomial::arithmetic::coefficient::PolynomialCoefficient;
15use crate::integer_polynomial::arithmetic::pow::addchains::{
16 addition_chain, addition_chain_is_shorter, pow_to_out_addchains,
17};
18use crate::integer_polynomial::arithmetic::pow::binexp::pow_to_out_binexp;
19use crate::integer_polynomial::arithmetic::pow::binomial::pow_to_out_binomial;
20use crate::integer_polynomial::arithmetic::pow::multinomial::pow_to_out_multinomial;
21use crate::integer_polynomial::arithmetic::pow::small::pow_to_out_small;
22use crate::integer_polynomial::arithmetic::square::square_to_out;
23use crate::integer_polynomial::arithmetic::vec::max_bits::vec_max_bits;
24use alloc::vec;
25use alloc::vec::Vec;
26use malachite_base::num::arithmetic::traits::{Pow, PowAssign};
27use malachite_base::num::conversion::traits::ExactFrom;
28
29pub mod addchains;
30pub mod binexp;
31pub mod binomial;
32pub mod multinomial;
33pub mod small;
34
35// Whether the multinomial recurrence is expected to beat repeated multiplication for a polynomial
36// of length `len`, at least 3, whose largest coefficient has `bits` significant bits, raised to the
37// power `e`. The recurrence costs about $e\,\ell^2$ multiplications by coefficients of the power,
38// which repeated multiplication avoids by working on whole polynomials, so it pays off only when
39// `e` is large relative to the length and the coefficients are small.
40//
41// The constants were measured on an Apple M-series machine with the tuner level `poly_pow_grid`.
42// FLINT's criterion, that the number of limbs is less than $(3e/2 + 150)/\ell$, is far from the
43// measured crossover here: it chooses the recurrence for every one-limb polynomial of length less
44// than 100, where repeated multiplication is often many times faster.
45pub(crate) fn multinomial_preferred(len: u64, bits: u64, e: u64) -> bool {
46 len <= 16
47 && len * bits <= 3072
48 && e >= (len << 2) * (bits >> 8).max(1)
49 && e.saturating_mul(bits) >= (len << 6).max(512)
50}
51
52// Whether the multinomial recurrence is expected to beat the binomial kernel for a polynomial of
53// length 2 whose largest coefficient has `bits` significant bits, raised to the power `e`. Each
54// step of the recurrence multiplies a coefficient of the power by a coefficient of the polynomial,
55// while the binomial kernel multiplies coefficients of the power by powers of the polynomial's
56// coefficients, which are as large; with small coefficients and large `e`, the recurrence wins.
57pub(crate) fn multinomial_preferred_for_binomial(bits: u64, e: u64) -> bool {
58 e >= 64 && (32..=1024).contains(&bits)
59}
60
61// Sets `out` to the coefficients of the `e`th power of the polynomial with coefficients `xs`, which
62// has length at least 2 and nonzero first and last elements, where `e` is at least 3. `out` must
63// have length `e * (xs.len() - 1) + 1`.
64//
65// Whether an addition chain is expected to beat binary exponentiation for a polynomial of length
66// `len`, at least 3, whose largest coefficient has `bits` significant bits, raised to the power
67// `e`, which is at least 5. A chain wins when it is shorter, and for $e \leq 7$ with coefficients
68// of at least 32 bits, where it is as long but its multiplications are more balanced. Binary
69// exponentiation's multiplications are all by the polynomial itself, which is cheap when the
70// polynomial is short and its coefficients small, so short polynomials need larger coefficients for
71// a chain to pay off.
72pub(crate) fn addition_chain_preferred(len: u64, bits: u64, e: u64) -> bool {
73 e <= 148
74 && ((e <= 7 && bits >= 32) || addition_chain_is_shorter(e))
75 && (len >= 8 || (len >= 4 && (bits >= 1024 || e <= 7)) || bits >= 4096)
76}
77
78// Below the fifth power, a square and a multiplication or two suffice. Otherwise the choice is
79// between the binomial kernel, for length 2; the multinomial recurrence, for large exponents and
80// small coefficients; addition chains; and binary exponentiation.
81//
82// This is equivalent to `_fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, except for the
83// criteria, which were measured, and except that FLINT never uses addition chains.
84crate_test_fn! {pow_to_out<C: PolynomialCoefficient>(out: &mut [C], xs: &[C], e: u64) {
85 let len = u64::exact_from(xs.len());
86 if e < 5 {
87 pow_to_out_small(out, xs, e);
88 return;
89 }
90 let bits = vec_max_bits(xs).0;
91 if len == 2 {
92 if multinomial_preferred_for_binomial(bits, e) {
93 pow_to_out_multinomial(out, xs, e);
94 } else {
95 pow_to_out_binomial(out, xs, e);
96 }
97 } else if multinomial_preferred(len, bits, e) {
98 pow_to_out_multinomial(out, xs, e);
99 } else if addition_chain_preferred(len, bits, e) {
100 pow_to_out_addchains_e(out, xs, e);
101 } else {
102 pow_to_out_binexp(out, xs, e);
103 }
104}}
105
106// Sets `out` to the coefficients of the `e`th power of the polynomial with coefficients `xs`, as
107// `pow_to_out` does, using an addition chain for `e`, which must be at most 148.
108//
109// This is equivalent to the main case of `fmpz_poly_pow_addchains` from
110// `fmpz_poly/pow_addchains.c`, FLINT 3.6.0.
111crate_test_fn! {pow_to_out_addchains_e<C: PolynomialCoefficient>(out: &mut [C], xs: &[C], e: u64) {
112 let (a, start) = addition_chain(e);
113 pow_to_out_addchains(out, xs, &a[start..]);
114}}
115
116// The length of the coefficient vector of the `e`th power of a polynomial with nonzero constant
117// term and length `len`, after a factor of $x^{e \ell}$, where $\ell$ is `low`.
118fn power_len(len: usize, low: usize, e: u64) -> usize {
119 let e = usize::exact_from(e);
120 e.checked_mul(len - 1 + low)
121 .and_then(|n| n.checked_add(1))
122 .expect("the power has too many coefficients to represent")
123}
124
125// Returns the coefficients of the `e`th power of the polynomial with coefficients `xs`, which has
126// no zeros at the end, using `pow_to_out_kernel` when the polynomial, once its factor of $x^\ell$
127// is removed, has length at least 2, and when `e` is at least 3.
128//
129// Writing the polynomial as $x^\ell q$, with $q_0 \neq 0$, its power is $x^{e\ell} q^e$, so only
130// $q$ is raised to the power `e` and the result is shifted. A kernel therefore always sees a
131// nonzero constant term, and a monomial $c x^\ell$ reduces to the power of a single coefficient.
132//
133// This is equivalent to `fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, and, with the
134// corresponding kernels, to `fmpz_poly_pow_multinomial`, `fmpz_poly_pow_binomial`,
135// `fmpz_poly_pow_binexp`, and `fmpz_poly_pow_addchains`, which share its special cases, except that
136// FLINT does not remove the factor of $x^\ell$, apart from in the multinomial kernel, which
137// requires a nonzero constant term.
138crate_test_fn! {pow_ref_with_kernel<C: PolynomialCoefficient>(
139 xs: &[C],
140 e: u64,
141 pow_to_out_kernel: fn(&mut [C], &[C], u64),
142) -> Vec<C> {
143 if e == 0 {
144 return vec![C::ONE];
145 }
146 let Some(low) = xs.iter().position(|x| !x.is_zero()) else {
147 return Vec::new();
148 };
149 let q = &xs[low..];
150 let mut out = vec![C::ZERO; power_len(q.len(), low, e)];
151 let out_q = &mut out[usize::exact_from(e) * low..];
152 match (q.len(), e) {
153 (1, _) => out_q[0] = q[0].pow_ref(e),
154 (_, 1) => out_q.clone_from_slice(q),
155 (_, 2) => square_to_out(out_q, q),
156 _ => pow_to_out_kernel(out_q, q, e),
157 }
158 out
159}}
160
161// This is equivalent to `fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, except as described
162// for `pow_ref_with_kernel`.
163#[inline]
164pub(crate) fn pow_ref<C: PolynomialCoefficient>(xs: &[C], e: u64) -> Vec<C> {
165 pow_ref_with_kernel(xs, e, pow_to_out)
166}
167
168// Replaces the coefficients `xs` of a polynomial with those of its `e`th power, reusing `xs` when
169// the power of a constant is computed or nothing changes.
170pub(crate) fn pow_assign_vec<C: PolynomialCoefficient + PowAssign<u64>>(xs: &mut Vec<C>, e: u64) {
171 match (xs.len(), e) {
172 (0, 0) => xs.push(C::ONE),
173 (_, 0) => {
174 xs.truncate(1);
175 xs[0] = C::ONE;
176 }
177 (0, _) | (_, 1) => {}
178 (1, _) => xs[0].pow_assign(e),
179 _ => *xs = pow_ref(xs, e),
180 }
181}
182
183impl Pow<u64> for IntegerPolynomial {
184 type Output = Self;
185
186 /// Raises an [`IntegerPolynomial`] to a power, taking it by value.
187 ///
188 /// $$
189 /// f(p, e) = p^e.
190 /// $$
191 ///
192 /// The zeroth power of every polynomial, including 0, is 1. Depending on the length of the
193 /// polynomial, the size of its coefficients, and the exponent, the power is computed by the
194 /// binomial theorem, by J. C. P. Miller's recurrence for the coefficients of a power, by an
195 /// addition chain, or by repeated squaring.
196 ///
197 /// # Worst-case complexity
198 /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
199 ///
200 /// $M(n, m) = O(n(m + \log n) \log (nm))$
201 ///
202 /// where $T$ is time, $M$ is additional memory, $n$ is `exp` times the length of the
203 /// polynomial, and $m$ is `exp` times the largest number of significant bits of any of its
204 /// coefficients.
205 ///
206 /// # Examples
207 /// ```
208 /// use core::str::FromStr;
209 /// use malachite_base::num::arithmetic::traits::Pow;
210 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
211 ///
212 /// assert_eq!(
213 /// IntegerPolynomial::from_str("x+1")
214 /// .unwrap()
215 /// .pow(3)
216 /// .to_string(),
217 /// "x^3+3*x^2+3*x+1"
218 /// );
219 /// assert_eq!(
220 /// IntegerPolynomial::from_str("2*x-1")
221 /// .unwrap()
222 /// .pow(4)
223 /// .to_string(),
224 /// "16*x^4-32*x^3+24*x^2-8*x+1"
225 /// );
226 /// assert_eq!(
227 /// IntegerPolynomial::from_str("x^2-x")
228 /// .unwrap()
229 /// .pow(0)
230 /// .to_string(),
231 /// "1"
232 /// );
233 /// ```
234 ///
235 /// This is equivalent to `fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, except that a
236 /// factor of $x^k$ is removed before powering, and that the algorithm is chosen by measured
237 /// criteria, which include addition chains.
238 #[inline]
239 fn pow(mut self, exp: u64) -> Self {
240 self.pow_assign(exp);
241 self
242 }
243}
244
245impl Pow<u64> for &IntegerPolynomial {
246 type Output = IntegerPolynomial;
247
248 /// Raises an [`IntegerPolynomial`] to a power, taking it by reference.
249 ///
250 /// $$
251 /// f(p, e) = p^e.
252 /// $$
253 ///
254 /// The zeroth power of every polynomial, including 0, is 1. Depending on the length of the
255 /// polynomial, the size of its coefficients, and the exponent, the power is computed by the
256 /// binomial theorem, by J. C. P. Miller's recurrence for the coefficients of a power, by an
257 /// addition chain, or by repeated squaring.
258 ///
259 /// # Worst-case complexity
260 /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
261 ///
262 /// $M(n, m) = O(n(m + \log n) \log (nm))$
263 ///
264 /// where $T$ is time, $M$ is additional memory, $n$ is `exp` times the length of the
265 /// polynomial, and $m$ is `exp` times the largest number of significant bits of any of its
266 /// coefficients.
267 ///
268 /// # Examples
269 /// ```
270 /// use core::str::FromStr;
271 /// use malachite_base::num::arithmetic::traits::Pow;
272 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
273 ///
274 /// assert_eq!(
275 /// (&IntegerPolynomial::from_str("x+1").unwrap())
276 /// .pow(3)
277 /// .to_string(),
278 /// "x^3+3*x^2+3*x+1"
279 /// );
280 /// assert_eq!(
281 /// (&IntegerPolynomial::from_str("2*x-1").unwrap())
282 /// .pow(4)
283 /// .to_string(),
284 /// "16*x^4-32*x^3+24*x^2-8*x+1"
285 /// );
286 /// assert_eq!(
287 /// (&IntegerPolynomial::from_str("x^2-x").unwrap())
288 /// .pow(0)
289 /// .to_string(),
290 /// "1"
291 /// );
292 /// ```
293 ///
294 /// This is equivalent to `fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, except that a
295 /// factor of $x^k$ is removed before powering, and that the algorithm is chosen by measured
296 /// criteria, which include addition chains.
297 #[inline]
298 fn pow(self, exp: u64) -> IntegerPolynomial {
299 IntegerPolynomial {
300 coefficients: pow_ref(&self.coefficients, exp),
301 }
302 }
303}
304
305impl PowAssign<u64> for IntegerPolynomial {
306 /// Raises an [`IntegerPolynomial`] to a power in place.
307 ///
308 /// $$
309 /// p \gets p^e.
310 /// $$
311 ///
312 /// The zeroth power of every polynomial, including 0, is 1. Depending on the length of the
313 /// polynomial, the size of its coefficients, and the exponent, the power is computed by the
314 /// binomial theorem, by J. C. P. Miller's recurrence for the coefficients of a power, by an
315 /// addition chain, or by repeated squaring.
316 ///
317 /// # Worst-case complexity
318 /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
319 ///
320 /// $M(n, m) = O(n(m + \log n) \log (nm))$
321 ///
322 /// where $T$ is time, $M$ is additional memory, $n$ is `exp` times the length of the
323 /// polynomial, and $m$ is `exp` times the largest number of significant bits of any of its
324 /// coefficients.
325 ///
326 /// # Examples
327 /// ```
328 /// use core::str::FromStr;
329 /// use malachite_base::num::arithmetic::traits::PowAssign;
330 /// use malachite_nz::integer_polynomial::IntegerPolynomial;
331 ///
332 /// let mut p = IntegerPolynomial::from_str("x+1").unwrap();
333 /// p.pow_assign(3);
334 /// assert_eq!(p.to_string(), "x^3+3*x^2+3*x+1");
335 ///
336 /// let mut p = IntegerPolynomial::from_str("2*x-1").unwrap();
337 /// p.pow_assign(4);
338 /// assert_eq!(p.to_string(), "16*x^4-32*x^3+24*x^2-8*x+1");
339 ///
340 /// let mut p = IntegerPolynomial::from_str("x^2-x").unwrap();
341 /// p.pow_assign(0);
342 /// assert_eq!(p.to_string(), "1");
343 /// ```
344 ///
345 /// This is equivalent to `fmpz_poly_pow` from `fmpz_poly/pow.c`, FLINT 3.6.0, except that a
346 /// factor of $x^k$ is removed before powering, and that the algorithm is chosen by measured
347 /// criteria, which include addition chains.
348 #[inline]
349 fn pow_assign(&mut self, exp: u64) {
350 pow_assign_vec(&mut self.coefficients, exp);
351 }
352}