Skip to main content

malachite_nz/integer_polynomial/arithmetic/
pow_truncated.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::{
15    PolynomialCoefficient, trim_coefficients, truncate_coefficients,
16};
17use crate::integer_polynomial::arithmetic::mul_truncated::mul_truncated_to_out;
18use crate::integer_polynomial::arithmetic::pow::binexp::binexp_start;
19use crate::integer_polynomial::arithmetic::square_truncated::square_truncated_to_out;
20use alloc::vec;
21use alloc::vec::Vec;
22use core::mem::swap;
23use malachite_base::num::arithmetic::traits::PowAssign;
24use malachite_base::num::conversion::traits::SaturatingFrom;
25use malachite_base::polynomial::{PowTruncated, PowTruncatedAssign};
26
27// Sets `out` to the first `out.len()` coefficients of the `e`th power of the polynomial with
28// coefficients `xs`, which is nonempty, where `e` is at least 3. `out.len()` must be positive and
29// at most `e * (xs.len() - 1) + 1`.
30//
31// This is left-to-right binary exponentiation with truncated squaring and multiplication,
32// alternating between `out` and a scratch buffer as in `pow_to_out_binexp`. Each intermediate power
33// is kept at its true length, capped at `out.len()`.
34//
35// This is equivalent to `_fmpz_poly_pow_trunc` from `fmpz_poly/pow_trunc.c`, FLINT 3.6.0, where `n`
36// is `out.len()`, except that FLINT pads the polynomial with zeros to length `n` and computes every
37// intermediate power to length `n`.
38crate_test_fn! {pow_truncated_to_out<C: PolynomialCoefficient>(out: &mut [C], xs: &[C], e: u64) {
39    let n = out.len();
40    let xs = &xs[..xs.len().min(n)];
41    let len = xs.len();
42    let mut v = vec![C::ZERO; n];
43    let (mut bit, swaps) = binexp_start(e);
44    let (mut r, mut s): (&mut [C], &mut [C]) = if swaps { (&mut v, out) } else { (out, &mut v) };
45    // The first step squares xs itself
46    let mut rlen = ((len << 1) - 1).min(n);
47    square_truncated_to_out(&mut r[..rlen], xs);
48    if bit & e != 0 {
49        let new_len = (rlen + len - 1).min(n);
50        mul_truncated_to_out(&mut s[..new_len], &r[..rlen], xs);
51        rlen = new_len;
52        swap(&mut r, &mut s);
53    }
54    loop {
55        bit >>= 1;
56        if bit == 0 {
57            break;
58        }
59        let new_len = ((rlen << 1) - 1).min(n);
60        square_truncated_to_out(&mut s[..new_len], &r[..rlen]);
61        rlen = new_len;
62        if bit & e != 0 {
63            let new_len = (rlen + len - 1).min(n);
64            mul_truncated_to_out(&mut r[..new_len], &s[..rlen], xs);
65            rlen = new_len;
66        } else {
67            swap(&mut r, &mut s);
68        }
69    }
70}}
71
72// Returns the coefficients of the `e`th power of the polynomial with coefficients `xs`, which has
73// no zeros at the end, keeping only the coefficients of $x^i$ for $i$ less than `len`, without
74// zeros at the end.
75//
76// Writing the polynomial modulo $x^n$ as $x^\ell q$, with $q_0 \neq 0$, its power is $x^{e\ell}
77// (q^e \bmod x^{n - e\ell})$, or 0 if $e\ell \geq n$.
78//
79// This is equivalent to `fmpz_poly_pow_trunc` from `fmpz_poly/pow_trunc.c`, FLINT 3.6.0, except
80// that it removes the factor of $x^\ell$, and that `n` is capped at the length of the untruncated
81// power, so that a `len` too large to allocate means no truncation.
82crate_test_fn! {pow_truncated_ref<C: PolynomialCoefficient>(xs: &[C], e: u64, len: u64) -> Vec<C> {
83    if len == 0 {
84        return Vec::new();
85    }
86    if e == 0 {
87        return vec![C::ONE];
88    }
89    let n = usize::saturating_from(len);
90    let xs = &xs[..xs.len().min(n)];
91    let Some(low) = xs.iter().position(|x| !x.is_zero()) else {
92        return Vec::new();
93    };
94    let shift = usize::saturating_from(e).saturating_mul(low);
95    if shift >= n {
96        return Vec::new();
97    }
98    let q = &xs[low..];
99    let q_len = (n - shift).min(
100        usize::saturating_from(e)
101            .saturating_mul(q.len() - 1)
102            .saturating_add(1),
103    );
104    let mut out = vec![C::ZERO; shift + q_len];
105    let out_q = &mut out[shift..];
106    match (q_len, e) {
107        (1, _) => out_q[0] = q[0].pow_ref(e),
108        (_, 1) => out_q.clone_from_slice(&q[..q_len]),
109        (_, 2) => square_truncated_to_out(out_q, q),
110        _ => pow_truncated_to_out(out_q, q, e),
111    }
112    trim_coefficients(&mut out);
113    out
114}}
115
116// Replaces the coefficients `xs` of a polynomial, which has no zeros at the end, with those of its
117// `e`th power truncated to length `len`, reusing `xs` when the power of a constant is computed or
118// the power is the polynomial itself.
119pub(crate) fn pow_truncated_assign_vec<C: PolynomialCoefficient + PowAssign<u64>>(
120    xs: &mut Vec<C>,
121    e: u64,
122    len: u64,
123) {
124    match (xs.len(), e, len) {
125        (_, _, 0) => xs.clear(),
126        (0, 0, _) => xs.push(C::ONE),
127        (_, 0, _) => {
128            xs.truncate(1);
129            xs[0] = C::ONE;
130        }
131        (0, _, _) => {}
132        (_, 1, _) => truncate_coefficients(xs, len),
133        (1, _, _) => xs[0].pow_assign(e),
134        _ => *xs = pow_truncated_ref(xs, e, len),
135    }
136}
137
138impl PowTruncated for IntegerPolynomial {
139    type Output = Self;
140
141    /// Raises an [`IntegerPolynomial`] to a power, keeping only the coefficients of $x^i$ for $i$
142    /// less than `len`, taking it by value.
143    ///
144    /// $$
145    /// f(p, e, n) = p^e \bmod x^n.
146    /// $$
147    ///
148    /// The polynomial need not already be truncated: this is the power of its image modulo $x^n$,
149    /// so only its first `len` coefficients are read. The zeroth power of every polynomial is 1,
150    /// truncated to 0 when `len` is 0. The power is computed by repeated truncated squaring and
151    /// multiplication.
152    ///
153    /// # Worst-case complexity
154    /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
155    ///
156    /// $M(n, m) = O(n(m + \log n) \log (nm))$
157    ///
158    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $m$ is `exp` times the
159    /// largest number of significant bits of any of the first `len` coefficients of the polynomial.
160    ///
161    /// # Examples
162    /// ```
163    /// use core::str::FromStr;
164    /// use malachite_base::polynomial::PowTruncated;
165    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
166    ///
167    /// assert_eq!(
168    ///     (IntegerPolynomial::from_str("x+1").unwrap())
169    ///         .pow_truncated(5, 3)
170    ///         .to_string(),
171    ///     "10*x^2+5*x+1"
172    /// );
173    /// // The power is a multiple of x^4.
174    /// assert_eq!(
175    ///     (IntegerPolynomial::from_str("x^2+x").unwrap())
176    ///         .pow_truncated(4, 4)
177    ///         .to_string(),
178    ///     "0"
179    /// );
180    /// ```
181    ///
182    /// This is equivalent to `fmpz_poly_pow_trunc` from `fmpz_poly/pow_trunc.c`, FLINT 3.6.0,
183    /// except that a factor of $x^k$ is removed before powering, and that the intermediate powers
184    /// are kept at their own lengths rather than padded to `len`.
185    #[inline]
186    fn pow_truncated(mut self, exp: u64, len: u64) -> Self {
187        self.pow_truncated_assign(exp, len);
188        self
189    }
190}
191
192impl PowTruncated for &IntegerPolynomial {
193    type Output = IntegerPolynomial;
194
195    /// Raises an [`IntegerPolynomial`] to a power, keeping only the coefficients of $x^i$ for $i$
196    /// less than `len`, taking it by reference.
197    ///
198    /// $$
199    /// f(p, e, n) = p^e \bmod x^n.
200    /// $$
201    ///
202    /// The polynomial need not already be truncated: this is the power of its image modulo $x^n$,
203    /// so only its first `len` coefficients are read. The zeroth power of every polynomial is 1,
204    /// truncated to 0 when `len` is 0. The power is computed by repeated truncated squaring and
205    /// multiplication.
206    ///
207    /// # Worst-case complexity
208    /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
209    ///
210    /// $M(n, m) = O(n(m + \log n) \log (nm))$
211    ///
212    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $m$ is `exp` times the
213    /// largest number of significant bits of any of the first `len` coefficients of the polynomial.
214    ///
215    /// # Examples
216    /// ```
217    /// use core::str::FromStr;
218    /// use malachite_base::polynomial::PowTruncated;
219    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
220    ///
221    /// assert_eq!(
222    ///     (&IntegerPolynomial::from_str("x+1").unwrap())
223    ///         .pow_truncated(5, 3)
224    ///         .to_string(),
225    ///     "10*x^2+5*x+1"
226    /// );
227    /// // The power is a multiple of x^4.
228    /// assert_eq!(
229    ///     (&IntegerPolynomial::from_str("x^2+x").unwrap())
230    ///         .pow_truncated(4, 4)
231    ///         .to_string(),
232    ///     "0"
233    /// );
234    /// ```
235    ///
236    /// This is equivalent to `fmpz_poly_pow_trunc` from `fmpz_poly/pow_trunc.c`, FLINT 3.6.0,
237    /// except that a factor of $x^k$ is removed before powering, and that the intermediate powers
238    /// are kept at their own lengths rather than padded to `len`.
239    #[inline]
240    fn pow_truncated(self, exp: u64, len: u64) -> IntegerPolynomial {
241        IntegerPolynomial {
242            coefficients: pow_truncated_ref(&self.coefficients, exp, len),
243        }
244    }
245}
246
247impl PowTruncatedAssign for IntegerPolynomial {
248    /// Raises an [`IntegerPolynomial`] to a power in place, keeping only the coefficients of $x^i$
249    /// for $i$ less than `len`.
250    ///
251    /// $$
252    /// p \gets p^e \bmod x^n.
253    /// $$
254    ///
255    /// The polynomial need not already be truncated: this is the power of its image modulo $x^n$,
256    /// so only its first `len` coefficients are read. The zeroth power of every polynomial is 1,
257    /// truncated to 0 when `len` is 0. The power is computed by repeated truncated squaring and
258    /// multiplication.
259    ///
260    /// # Worst-case complexity
261    /// $T(n, m) = O(n(m + \log n) \log (nm) \log\log (nm))$
262    ///
263    /// $M(n, m) = O(n(m + \log n) \log (nm))$
264    ///
265    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $m$ is `exp` times the
266    /// largest number of significant bits of any of the first `len` coefficients of the polynomial.
267    ///
268    /// # Examples
269    /// ```
270    /// use core::str::FromStr;
271    /// use malachite_base::polynomial::PowTruncatedAssign;
272    /// use malachite_nz::integer_polynomial::IntegerPolynomial;
273    ///
274    /// let mut p = IntegerPolynomial::from_str("x+1").unwrap();
275    /// p.pow_truncated_assign(5, 3);
276    /// assert_eq!(p.to_string(), "10*x^2+5*x+1");
277    ///
278    /// // The power is a multiple of x^4.
279    /// let mut p = IntegerPolynomial::from_str("x^2+x").unwrap();
280    /// p.pow_truncated_assign(4, 4);
281    /// assert_eq!(p.to_string(), "0");
282    /// ```
283    ///
284    /// This is equivalent to `fmpz_poly_pow_trunc` from `fmpz_poly/pow_trunc.c`, FLINT 3.6.0,
285    /// except that a factor of $x^k$ is removed before powering, and that the intermediate powers
286    /// are kept at their own lengths rather than padded to `len`.
287    #[inline]
288    fn pow_truncated_assign(&mut self, exp: u64, len: u64) {
289        pow_truncated_assign_vec(&mut self.coefficients, exp, len);
290    }
291}