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