Skip to main content

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}