Skip to main content

malachite_base/unsigned_polynomial/arithmetic/
mod_pow_truncated.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::traits::ModIsReduced;
10use crate::num::basic::traits::Zero;
11use crate::num::basic::unsigneds::PrimitiveUnsigned;
12use crate::num::conversion::traits::{ExactFrom, SaturatingFrom};
13use crate::polynomial::{ModPowTruncated, ModPowTruncatedAssign, Polynomial, pow_binexp_trimmed};
14use crate::unsigned_polynomial::UnsignedPolynomial;
15use crate::unsigned_polynomial::arithmetic::mod_mul_truncated::mod_mul_truncated_helper;
16use crate::unsigned_polynomial::arithmetic::mod_square_truncated::mod_square_truncated_helper;
17use alloc::vec;
18use alloc::vec::Vec;
19
20fn assert_reduced<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, m: T) {
21    assert!(
22        p.mod_is_reduced(&m),
23        "self must be reduced mod m, but {p} has a coefficient >= {m}"
24    );
25}
26
27// The coefficients, without zeros at the end, of the `e`th power modulo `m` of the polynomial with
28// coefficients `xs`, which has length at least 2, a nonzero first element, and coefficients reduced
29// modulo $m$, keeping only the coefficients of $x^i$ for $i$ less than `len`, which is at least 2,
30// where `e` is at least 3, by binary exponentiation with truncated squaring and multiplication,
31// each reduced and trimmed.
32//
33// This is equivalent to `_nmod_poly_pow_trunc_binexp` from `nmod_poly/pow_trunc.c`, FLINT 3.6.0, ,
34// except that the polynomial is not padded to length `len` and the intermediate powers are trimmed.
35crate_test_fn! {mod_pow_truncated_binexp<T: PrimitiveUnsigned>(
36    xs: &[T],
37    e: u64,
38    len: u64,
39    m: T,
40) -> Vec<T> {
41    pow_binexp_trimmed(
42        xs,
43        e,
44        |r| mod_square_truncated_helper(r, len, m).into_coefficients_asc(),
45        |r, xs| mod_mul_truncated_helper(r, xs, len, m).into_coefficients_asc(),
46    )
47}}
48
49// The `e`th power modulo `m` of the polynomial with coefficients `xs`, which has no zeros at the
50// end and coefficients reduced modulo $m$, keeping only the coefficients of $x^i$ for $i$ less than
51// `len`.
52//
53// Writing the polynomial modulo $x^n$ as $x^\ell q$, with $q_0 \neq 0$, its power is $x^{e\ell}
54// (q^e \bmod x^{n - e\ell})$, or 0 if $e\ell \geq n$.
55//
56// This is equivalent to `nmod_poly_pow_trunc` from `nmod_poly/pow_trunc.c`, FLINT 3.6.0, with the
57// modulus $m$, except for the removal of the factor of $x^\ell$, and except that the zeroth power
58// of the zero polynomial is 1, where FLINT gives 0.
59fn mod_pow_truncated_helper<T: PrimitiveUnsigned>(
60    xs: &[T],
61    e: u64,
62    len: u64,
63    m: T,
64) -> UnsignedPolynomial<T> {
65    if m == T::ONE || len == 0 {
66        return UnsignedPolynomial::ZERO;
67    }
68    if e == 0 {
69        return UnsignedPolynomial::one();
70    }
71    let n = usize::saturating_from(len);
72    let xs = &xs[..xs.len().min(n)];
73    let Some(low) = xs.iter().position(|&x| x != T::ZERO) else {
74        return UnsignedPolynomial::ZERO;
75    };
76    let shift = usize::saturating_from(e).saturating_mul(low);
77    if shift >= n {
78        return UnsignedPolynomial::ZERO;
79    }
80    let q = &xs[low..];
81    let q_len = n - shift;
82    let q_len_u64 = u64::exact_from(q_len);
83    let mut power = match (q.len().min(q_len), e) {
84        (1, _) => {
85            let c = q[0].mod_pow(e, m);
86            if c == T::ZERO { Vec::new() } else { vec![c] }
87        }
88        (m, 1) => {
89            let mut power = q[..m].to_vec();
90            while power.last() == Some(&T::ZERO) {
91                power.pop();
92            }
93            power
94        }
95        (_, 2) => mod_square_truncated_helper(q, q_len_u64, m).into_coefficients_asc(),
96        _ => mod_pow_truncated_binexp(q, e, q_len_u64, m),
97    };
98    if power.is_empty() {
99        return UnsignedPolynomial::ZERO;
100    }
101    power.splice(0..0, core::iter::repeat_n(T::ZERO, shift));
102    UnsignedPolynomial {
103        coefficients: power,
104    }
105}
106
107impl<T: PrimitiveUnsigned> ModPowTruncated<T> for UnsignedPolynomial<T> {
108    type Output = Self;
109
110    /// Raises an [`UnsignedPolynomial`] to a power modulo $m$, keeping only the coefficients of
111    /// $x^i$ for $i$ less than `len`, taking it by value. Its coefficients must already be reduced
112    /// modulo $m$.
113    ///
114    /// $$
115    /// f(p, e, n, k) = (p^e \bmod x^n) \bmod m.
116    /// $$
117    ///
118    /// The polynomial need not already be truncated: only its first `len` coefficients are read.
119    /// The zeroth power of every polynomial is 1, truncated to 0 when `len` is 0 and reduced to 0
120    /// when $k$ is 0. The power is computed by repeated truncated squaring modulo $m$.
121    ///
122    /// # Worst-case complexity
123    /// $T(n) = O(n^{\log_2 3} \log e)$
124    ///
125    /// $M(n) = O(n)$
126    ///
127    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $e$ is `exp`.
128    ///
129    /// # Panics
130    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
131    ///
132    /// # Examples
133    /// ```
134    /// use core::str::FromStr;
135    /// use malachite_base::polynomial::ModPowTruncated;
136    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
137    ///
138    /// assert_eq!(
139    ///     (UnsignedPolynomial::<u8>::from_str("x+1").unwrap())
140    ///         .mod_pow_truncated(5, 3, 7)
141    ///         .to_string(),
142    ///     "3*x^2+5*x+1"
143    /// );
144    /// // The power is a multiple of x^4.
145    /// assert_eq!(
146    ///     (UnsignedPolynomial::<u8>::from_str("x^2+x").unwrap())
147    ///         .mod_pow_truncated(4, 4, 7)
148    ///         .to_string(),
149    ///     "0"
150    /// );
151    /// ```
152    ///
153    /// This is equivalent to `nmod_poly_pow_trunc` from `nmod_poly/pow_trunc.c`, FLINT 3.6.0,
154    /// except that a factor of $x^\ell$ is removed before powering, that the intermediate powers
155    /// are trimmed rather than padded to `len`, and that the zeroth power of the zero polynomial is
156    /// 1 (modulo $m$), where FLINT gives 0.
157    #[inline]
158    fn mod_pow_truncated(mut self, exp: u64, len: u64, m: T) -> Self {
159        self.mod_pow_truncated_assign(exp, len, m);
160        self
161    }
162}
163
164impl<T: PrimitiveUnsigned> ModPowTruncated<T> for &UnsignedPolynomial<T> {
165    type Output = UnsignedPolynomial<T>;
166
167    /// Raises an [`UnsignedPolynomial`] to a power modulo $m$, keeping only the coefficients of
168    /// $x^i$ for $i$ less than `len`, taking it by reference. Its coefficients must already be
169    /// reduced modulo $m$.
170    ///
171    /// $$
172    /// f(p, e, n, k) = (p^e \bmod x^n) \bmod m.
173    /// $$
174    ///
175    /// The polynomial need not already be truncated: only its first `len` coefficients are read.
176    /// The zeroth power of every polynomial is 1, truncated to 0 when `len` is 0 and reduced to 0
177    /// when $k$ is 0. The power is computed by repeated truncated squaring modulo $m$.
178    ///
179    /// # Worst-case complexity
180    /// $T(n) = O(n^{\log_2 3} \log e)$
181    ///
182    /// $M(n) = O(n)$
183    ///
184    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $e$ is `exp`.
185    ///
186    /// # Panics
187    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
188    ///
189    /// # Examples
190    /// ```
191    /// use core::str::FromStr;
192    /// use malachite_base::polynomial::ModPowTruncated;
193    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
194    ///
195    /// assert_eq!(
196    ///     (&UnsignedPolynomial::<u8>::from_str("x+1").unwrap())
197    ///         .mod_pow_truncated(5, 3, 7)
198    ///         .to_string(),
199    ///     "3*x^2+5*x+1"
200    /// );
201    /// // The power is a multiple of x^4.
202    /// assert_eq!(
203    ///     (&UnsignedPolynomial::<u8>::from_str("x^2+x").unwrap())
204    ///         .mod_pow_truncated(4, 4, 7)
205    ///         .to_string(),
206    ///     "0"
207    /// );
208    /// ```
209    ///
210    /// This is equivalent to `nmod_poly_pow_trunc` from `nmod_poly/pow_trunc.c`, FLINT 3.6.0,
211    /// except that a factor of $x^\ell$ is removed before powering, that the intermediate powers
212    /// are trimmed rather than padded to `len`, and that the zeroth power of the zero polynomial is
213    /// 1 (modulo $m$), where FLINT gives 0.
214    fn mod_pow_truncated(self, exp: u64, len: u64, m: T) -> UnsignedPolynomial<T> {
215        assert_reduced(self, m);
216        mod_pow_truncated_helper(&self.coefficients, exp, len, m)
217    }
218}
219
220impl<T: PrimitiveUnsigned> ModPowTruncatedAssign<T> for UnsignedPolynomial<T> {
221    /// Raises an [`UnsignedPolynomial`] to a power modulo $m$ in place, keeping only the
222    /// coefficients of $x^i$ for $i$ less than `len`. Its coefficients must already be reduced
223    /// modulo $m$.
224    ///
225    /// $$
226    /// p \gets (p^e \bmod x^n) \bmod m.
227    /// $$
228    ///
229    /// The polynomial need not already be truncated: only its first `len` coefficients are read.
230    /// The zeroth power of every polynomial is 1, truncated to 0 when `len` is 0 and reduced to 0
231    /// when $k$ is 0. The power is computed by repeated truncated squaring modulo $m$.
232    ///
233    /// # Worst-case complexity
234    /// $T(n) = O(n^{\log_2 3} \log e)$
235    ///
236    /// $M(n) = O(n)$
237    ///
238    /// where $T$ is time, $M$ is additional memory, $n$ is `len`, and $e$ is `exp`.
239    ///
240    /// # Panics
241    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
242    ///
243    /// # Examples
244    /// ```
245    /// use core::str::FromStr;
246    /// use malachite_base::polynomial::ModPowTruncatedAssign;
247    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
248    ///
249    /// let mut p = UnsignedPolynomial::<u8>::from_str("x+1").unwrap();
250    /// p.mod_pow_truncated_assign(5, 3, 7);
251    /// assert_eq!(p.to_string(), "3*x^2+5*x+1");
252    ///
253    /// // The power is a multiple of x^4.
254    /// let mut p = UnsignedPolynomial::<u8>::from_str("x^2+x").unwrap();
255    /// p.mod_pow_truncated_assign(4, 4, 7);
256    /// assert_eq!(p.to_string(), "0");
257    /// ```
258    ///
259    /// This is equivalent to `nmod_poly_pow_trunc` from `nmod_poly/pow_trunc.c`, FLINT 3.6.0,
260    /// except that a factor of $x^\ell$ is removed before powering, that the intermediate powers
261    /// are trimmed rather than padded to `len`, and that the zeroth power of the zero polynomial is
262    /// 1 (modulo $m$), where FLINT gives 0.
263    fn mod_pow_truncated_assign(&mut self, exp: u64, len: u64, m: T) {
264        assert_reduced(self, m);
265        let xs = &mut self.coefficients;
266        match (xs.len(), exp, len, m == T::ONE) {
267            (_, _, 0, _) | (_, _, _, true) => xs.clear(),
268            (0, 0, _, _) => xs.push(T::ONE),
269            (_, 0, _, _) => {
270                xs.truncate(1);
271                xs[0] = T::ONE;
272            }
273            (0, _, _, _) => {}
274            (_, 1, _, _) => self.truncate_assign(len),
275            (1, _, _, _) => {
276                xs[0].mod_pow_assign(exp, m);
277                if xs[0] == T::ZERO {
278                    xs.clear();
279                }
280            }
281            _ => *self = mod_pow_truncated_helper(xs, exp, len, m),
282        }
283    }
284}