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}