Skip to main content

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