Skip to main content

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}