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