malachite_base/unsigned_polynomial/arithmetic/mod_power_of_2_integral.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, Parity};
10use crate::num::basic::traits::Zero;
11use crate::num::basic::unsigneds::PrimitiveUnsigned;
12use crate::polynomial::{ModPowerOf2Integral, ModPowerOf2IntegralAssign};
13use crate::unsigned_polynomial::UnsignedPolynomial;
14use alloc::vec;
15use alloc::vec::Vec;
16
17fn assert_reduced<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, pow: u64) {
18 assert!(pow <= T::WIDTH);
19 assert!(
20 p.mod_power_of_2_is_reduced(pow),
21 "self must be reduced mod 2^pow, but {p} has a coefficient >= 2^{pow}"
22 );
23}
24
25// The coefficients of the integral modulo $2^k$, where $k$ is `pow`, of the polynomial with
26// coefficients `xs`, which is nonempty and reduced modulo $2^k$. The coefficient of $x^{i-1}$,
27// divided by $i$, goes to $x^i$; a zero coefficient stays zero. The indices of the nonzero
28// coefficients must all be odd, so their product is a unit, and all the divisions share its
29// inverse, as in `mod_integral`. Arithmetic modulo $2^\text{W}$, with indices converted by
30// wrapping, loses nothing, since $2^k$ divides $2^\text{W}$. The result is not trimmed.
31fn mod_power_of_2_integral_coefficients<T: PrimitiveUnsigned>(xs: &[T], pow: u64) -> Vec<T> {
32 let n = xs.len();
33 let mut out = vec![T::ZERO; n + 1];
34 out[1..].copy_from_slice(xs);
35 // The product of the indices above i whose coefficients are nonzero.
36 let mut product = T::ONE.mod_power_of_2(pow);
37 for (i, c) in out.iter_mut().enumerate().skip(2).rev() {
38 if *c != T::ZERO {
39 assert!(
40 i.odd(),
41 "The integral modulo 2^pow is only defined if every nonzero coefficient belongs to \
42 an even power of x, but the coefficient of x^{} is nonzero",
43 i - 1
44 );
45 *c = c.wrapping_mul(product).mod_power_of_2(pow);
46 product = product
47 .wrapping_mul(T::wrapping_from(i))
48 .mod_power_of_2(pow);
49 }
50 }
51 if n >= 2 {
52 // `product` is odd, so it has an inverse.
53 let mut inverse = product.mod_power_of_2_inverse(pow).unwrap();
54 for (i, c) in out.iter_mut().enumerate().skip(2) {
55 if *c != T::ZERO {
56 *c = c.wrapping_mul(inverse).mod_power_of_2(pow);
57 inverse = inverse
58 .wrapping_mul(T::wrapping_from(i))
59 .mod_power_of_2(pow);
60 }
61 }
62 }
63 out
64}
65
66fn mod_power_of_2_integral_ref<T: PrimitiveUnsigned>(
67 p: &UnsignedPolynomial<T>,
68 pow: u64,
69) -> UnsignedPolynomial<T> {
70 assert_reduced(p, pow);
71 if p.coefficients.is_empty() {
72 return UnsignedPolynomial::ZERO;
73 }
74 let mut q = UnsignedPolynomial {
75 coefficients: mod_power_of_2_integral_coefficients(&p.coefficients, pow),
76 };
77 q.trim();
78 q
79}
80
81impl<T: PrimitiveUnsigned> ModPowerOf2Integral for UnsignedPolynomial<T> {
82 type Output = Self;
83
84 /// Computes the integral modulo $2^k$ of an [`UnsignedPolynomial`] whose constant term is zero,
85 /// taking the polynomial by value. Its coefficients must already be reduced modulo $2^k$.
86 ///
87 /// $$
88 /// f(p, k) = \int_0^x p(t)\,dt \bmod 2^k = \sum_{i=0}^{n-1} \frac{a_i}{i+1}x^{i+1} \bmod 2^k.
89 /// $$
90 ///
91 /// The coefficient of $x^{i-1}$ is divided by $i$ and moved to $x^i$. Only odd numbers are
92 /// units modulo $2^k$, so every nonzero coefficient must belong to an even power of $x$; a zero
93 /// coefficient stays zero. The divisions share a single inversion modulo $2^k$. The integral of
94 /// zero is zero, for every $k$.
95 ///
96 /// # Worst-case complexity
97 /// $T(n) = O(n)$
98 ///
99 /// $M(n) = O(n)$
100 ///
101 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
102 ///
103 /// # Panics
104 /// Panics if `pow` is greater than `T::WIDTH`, if any coefficient is greater than or equal to
105 /// $2^k$, or if the coefficient of an odd power of $x$ is nonzero.
106 ///
107 /// # Examples
108 /// ```
109 /// use core::str::FromStr;
110 /// use malachite_base::polynomial::ModPowerOf2Integral;
111 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
112 ///
113 /// // Dividing by 3 is multiplying by 3 modulo 8.
114 /// assert_eq!(
115 /// UnsignedPolynomial::<u8>::from_str("x^2")
116 /// .unwrap()
117 /// .mod_power_of_2_integral(3)
118 /// .to_string(),
119 /// "3*x^3"
120 /// );
121 /// // Dividing by 5 is multiplying by 13 modulo 16.
122 /// assert_eq!(
123 /// UnsignedPolynomial::<u8>::from_str("x^4+1")
124 /// .unwrap()
125 /// .mod_power_of_2_integral(4)
126 /// .to_string(),
127 /// "13*x^5+x"
128 /// );
129 /// ```
130 ///
131 /// This is `nmod_poly_integral` from `nmod_poly/integral.c`, FLINT 3.6.0, with the modulus
132 /// $2^k$, except that FLINT needs every index from 1 to the degree plus 1 to be a unit, even
133 /// when its coefficient is zero, and so cannot integrate any polynomial of degree at least 1
134 /// modulo $2^k$.
135 #[inline]
136 fn mod_power_of_2_integral(self, pow: u64) -> Self {
137 mod_power_of_2_integral_ref(&self, pow)
138 }
139}
140
141impl<T: PrimitiveUnsigned> ModPowerOf2Integral for &UnsignedPolynomial<T> {
142 type Output = UnsignedPolynomial<T>;
143
144 /// Computes the integral modulo $2^k$ of an [`UnsignedPolynomial`] whose constant term is zero,
145 /// taking the polynomial by reference. Its coefficients must already be reduced modulo $2^k$.
146 ///
147 /// $$
148 /// f(p, k) = \int_0^x p(t)\,dt \bmod 2^k = \sum_{i=0}^{n-1} \frac{a_i}{i+1}x^{i+1} \bmod 2^k.
149 /// $$
150 ///
151 /// The coefficient of $x^{i-1}$ is divided by $i$ and moved to $x^i$. Only odd numbers are
152 /// units modulo $2^k$, so every nonzero coefficient must belong to an even power of $x$; a zero
153 /// coefficient stays zero. The divisions share a single inversion modulo $2^k$. The integral of
154 /// zero is zero, for every $k$.
155 ///
156 /// # Worst-case complexity
157 /// $T(n) = O(n)$
158 ///
159 /// $M(n) = O(n)$
160 ///
161 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
162 ///
163 /// # Panics
164 /// Panics if `pow` is greater than `T::WIDTH`, if any coefficient is greater than or equal to
165 /// $2^k$, or if the coefficient of an odd power of $x$ is nonzero.
166 ///
167 /// # Examples
168 /// ```
169 /// use core::str::FromStr;
170 /// use malachite_base::polynomial::ModPowerOf2Integral;
171 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
172 ///
173 /// // Dividing by 3 is multiplying by 3 modulo 8.
174 /// assert_eq!(
175 /// (&UnsignedPolynomial::<u8>::from_str("x^2").unwrap())
176 /// .mod_power_of_2_integral(3)
177 /// .to_string(),
178 /// "3*x^3"
179 /// );
180 /// // Dividing by 5 is multiplying by 13 modulo 16.
181 /// assert_eq!(
182 /// (&UnsignedPolynomial::<u8>::from_str("x^4+1").unwrap())
183 /// .mod_power_of_2_integral(4)
184 /// .to_string(),
185 /// "13*x^5+x"
186 /// );
187 /// ```
188 ///
189 /// This is `nmod_poly_integral` from `nmod_poly/integral.c`, FLINT 3.6.0, with the modulus
190 /// $2^k$, except that FLINT needs every index from 1 to the degree plus 1 to be a unit, even
191 /// when its coefficient is zero, and so cannot integrate any polynomial of degree at least 1
192 /// modulo $2^k$.
193 #[inline]
194 fn mod_power_of_2_integral(self, pow: u64) -> UnsignedPolynomial<T> {
195 mod_power_of_2_integral_ref(self, pow)
196 }
197}
198
199impl<T: PrimitiveUnsigned> ModPowerOf2IntegralAssign for UnsignedPolynomial<T> {
200 /// Replaces an [`UnsignedPolynomial`] with its integral modulo $2^k$ whose constant term is
201 /// zero. Its coefficients must already be reduced modulo $2^k$.
202 ///
203 /// $$
204 /// p \gets \int_0^x p(t)\,dt \bmod 2^k = \sum_{i=0}^{n-1} \frac{a_i}{i+1}x^{i+1} \bmod 2^k.
205 /// $$
206 ///
207 /// The coefficient of $x^{i-1}$ is divided by $i$ and moved to $x^i$. Only odd numbers are
208 /// units modulo $2^k$, so every nonzero coefficient must belong to an even power of $x$; a zero
209 /// coefficient stays zero. The divisions share a single inversion modulo $2^k$. The integral of
210 /// zero is zero, for every $k$.
211 ///
212 /// # Worst-case complexity
213 /// $T(n) = O(n)$
214 ///
215 /// $M(n) = O(n)$
216 ///
217 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
218 ///
219 /// # Panics
220 /// Panics if `pow` is greater than `T::WIDTH`, if any coefficient is greater than or equal to
221 /// $2^k$, or if the coefficient of an odd power of $x$ is nonzero.
222 ///
223 /// # Examples
224 /// ```
225 /// use core::str::FromStr;
226 /// use malachite_base::polynomial::ModPowerOf2IntegralAssign;
227 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
228 ///
229 /// let mut p = UnsignedPolynomial::<u8>::from_str("x^2").unwrap();
230 /// p.mod_power_of_2_integral_assign(3);
231 /// assert_eq!(p.to_string(), "3*x^3");
232 /// ```
233 ///
234 /// This is `nmod_poly_integral` from `nmod_poly/integral.c`, FLINT 3.6.0, with the modulus
235 /// $2^k$, except that FLINT needs every index from 1 to the degree plus 1 to be a unit, even
236 /// when its coefficient is zero, and so cannot integrate any polynomial of degree at least 1
237 /// modulo $2^k$.
238 #[inline]
239 fn mod_power_of_2_integral_assign(&mut self, pow: u64) {
240 *self = mod_power_of_2_integral_ref(self, pow);
241 }
242}