Skip to main content

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}