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