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}