Skip to main content

malachite_base/unsigned_polynomial/arithmetic/
mod_nth_derivative.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::unsigneds::PrimitiveUnsigned;
11use crate::num::conversion::traits::ExactFrom;
12use crate::polynomial::{ModNthDerivative, ModNthDerivativeAssign};
13use crate::unsigned_polynomial::UnsignedPolynomial;
14
15fn assert_reduced<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, m: T) {
16    assert!(
17        p.mod_is_reduced(&m),
18        "self must be reduced mod m, but {p} has a coefficient >= {m}"
19    );
20}
21
22// Adds 1 to a counter modulo m.
23fn increment_mod<T: PrimitiveUnsigned>(i: &mut T, m: T) {
24    *i += T::ONE;
25    if *i == m {
26        *i = T::ZERO;
27    }
28}
29
30// Multiplies the coefficient of x^i, for i at least n, by the falling factorial i(i - 1)...(i - n +
31// 1) modulo m and moves it to x^(i - n). The division that carries one falling factorial to the
32// next need not be possible modulo m, so each is multiplied out from its n factors, which are
33// counted down from i mod m. They are all multiples of n!, so n! mod m is computed first, and if it
34// is zero, so is the result. The products can be zero, so the result is trimmed.
35fn mod_nth_derivative_in_place<T: PrimitiveUnsigned>(p: &mut UnsignedPolynomial<T>, n: u64, m: T) {
36    assert_reduced(p, m);
37    if n == 0 {
38        return;
39    }
40    if u64::exact_from(p.coefficients.len()) <= n {
41        p.coefficients.clear();
42        return;
43    }
44    // After this loop, `top` is n mod m.
45    let mut top = T::ZERO;
46    let mut factorial = T::ONE;
47    for _ in 0..n {
48        increment_mod(&mut top, m);
49        factorial.mod_mul_assign(top, m);
50    }
51    if factorial == T::ZERO {
52        p.coefficients.clear();
53        return;
54    }
55    p.coefficients.drain(..usize::exact_from(n));
56    for c in &mut p.coefficients {
57        let mut falling = T::ONE;
58        let mut factor = top;
59        for _ in 0..n {
60            falling.mod_mul_assign(factor, m);
61            factor = if factor == T::ZERO {
62                m - T::ONE
63            } else {
64                factor - T::ONE
65            };
66        }
67        c.mod_mul_assign(falling, m);
68        increment_mod(&mut top, m);
69    }
70    p.trim();
71}
72
73impl<T: PrimitiveUnsigned> ModNthDerivative<T> for UnsignedPolynomial<T> {
74    type Output = Self;
75
76    /// Computes the $n$th derivative of an [`UnsignedPolynomial`] modulo `m`, taking the polynomial
77    /// by value. The coefficients must already be reduced modulo `m`.
78    ///
79    /// $$
80    /// f(p, n, m) = p^{(n)} \bmod m.
81    /// $$
82    ///
83    /// The coefficient of $x^i$ is multiplied by the falling factorial $i^{\underline n} =
84    /// i(i-1)\cdots(i-n+1)$, reduced, and moved to $x^{i-n}$. The products can be zero even when
85    /// the coefficients are not, so the derivative can lose any number of degrees; if `m` divides
86    /// $n!$, every falling factorial is a multiple of the modulus, and the result is zero.
87    ///
88    /// # Worst-case complexity
89    /// $T(k, n) = O(kn)$
90    ///
91    /// $M(k) = O(k)$
92    ///
93    /// where $T$ is time, $M$ is additional memory, $k$ is `self.len()`, and $n$ is `n`.
94    ///
95    /// # Panics
96    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
97    ///
98    /// # Examples
99    /// ```
100    /// use core::str::FromStr;
101    /// use malachite_base::polynomial::ModNthDerivative;
102    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
103    ///
104    /// let p = UnsignedPolynomial::<u8>::from_str("x^4+3*x^3+2*x+4").unwrap();
105    /// assert_eq!(p.clone().mod_nth_derivative(2, 5).to_string(), "2*x^2+3*x");
106    /// assert_eq!(p.mod_nth_derivative(0, 5).to_string(), "x^4+3*x^3+2*x+4");
107    ///
108    /// // 6 divides 3!.
109    /// let p = UnsignedPolynomial::<u8>::from_str("x^4+x^3").unwrap();
110    /// assert_eq!(p.mod_nth_derivative(3, 6).to_string(), "0");
111    /// ```
112    ///
113    /// FLINT has no `nmod_poly_nth_derivative`; this computes the same multipliers as
114    /// `fmpz_poly_nth_derivative` from `fmpz_poly/nth_derivative.c`, FLINT 3.6.0, directly modulo
115    /// `m`.
116    #[inline]
117    fn mod_nth_derivative(mut self, n: u64, m: T) -> Self {
118        mod_nth_derivative_in_place(&mut self, n, m);
119        self
120    }
121}
122
123impl<T: PrimitiveUnsigned> ModNthDerivative<T> for &UnsignedPolynomial<T> {
124    type Output = UnsignedPolynomial<T>;
125
126    /// Computes the $n$th derivative of an [`UnsignedPolynomial`] modulo `m`, taking the polynomial
127    /// by reference. The coefficients must already be reduced modulo `m`.
128    ///
129    /// $$
130    /// f(p, n, m) = p^{(n)} \bmod m.
131    /// $$
132    ///
133    /// The coefficient of $x^i$ is multiplied by the falling factorial $i^{\underline n} =
134    /// i(i-1)\cdots(i-n+1)$, reduced, and moved to $x^{i-n}$. The products can be zero even when
135    /// the coefficients are not, so the derivative can lose any number of degrees; if `m` divides
136    /// $n!$, every falling factorial is a multiple of the modulus, and the result is zero.
137    ///
138    /// # Worst-case complexity
139    /// $T(k, n) = O(kn)$
140    ///
141    /// $M(k) = O(k)$
142    ///
143    /// where $T$ is time, $M$ is additional memory, $k$ is `self.len()`, and $n$ is `n`.
144    ///
145    /// # Panics
146    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
147    ///
148    /// # Examples
149    /// ```
150    /// use core::str::FromStr;
151    /// use malachite_base::polynomial::ModNthDerivative;
152    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
153    ///
154    /// let p = UnsignedPolynomial::<u8>::from_str("x^4+3*x^3+2*x+4").unwrap();
155    /// assert_eq!((&p).mod_nth_derivative(2, 5).to_string(), "2*x^2+3*x");
156    /// assert_eq!((&p).mod_nth_derivative(0, 5).to_string(), "x^4+3*x^3+2*x+4");
157    ///
158    /// // 6 divides 3!.
159    /// let p = UnsignedPolynomial::<u8>::from_str("x^4+x^3").unwrap();
160    /// assert_eq!((&p).mod_nth_derivative(3, 6).to_string(), "0");
161    /// ```
162    ///
163    /// FLINT has no `nmod_poly_nth_derivative`; this computes the same multipliers as
164    /// `fmpz_poly_nth_derivative` from `fmpz_poly/nth_derivative.c`, FLINT 3.6.0, directly modulo
165    /// `m`.
166    #[inline]
167    fn mod_nth_derivative(self, n: u64, m: T) -> UnsignedPolynomial<T> {
168        let mut p = self.clone();
169        mod_nth_derivative_in_place(&mut p, n, m);
170        p
171    }
172}
173
174impl<T: PrimitiveUnsigned> ModNthDerivativeAssign<T> for UnsignedPolynomial<T> {
175    /// Replaces an [`UnsignedPolynomial`] with its $n$th derivative modulo `m`, in place. The
176    /// coefficients must already be reduced modulo `m`.
177    ///
178    /// $$
179    /// p \gets p^{(n)} \bmod m.
180    /// $$
181    ///
182    /// The coefficient of $x^i$ is multiplied by the falling factorial $i^{\underline n} =
183    /// i(i-1)\cdots(i-n+1)$, reduced, and moved to $x^{i-n}$. The products can be zero even when
184    /// the coefficients are not, so the derivative can lose any number of degrees; if `m` divides
185    /// $n!$, every falling factorial is a multiple of the modulus, and the result is zero.
186    ///
187    /// # Worst-case complexity
188    /// $T(k, n) = O(kn)$
189    ///
190    /// $M(k) = O(k)$
191    ///
192    /// where $T$ is time, $M$ is additional memory, $k$ is `self.len()`, and $n$ is `n`.
193    ///
194    /// # Panics
195    /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
196    ///
197    /// # Examples
198    /// ```
199    /// use core::str::FromStr;
200    /// use malachite_base::polynomial::ModNthDerivativeAssign;
201    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
202    ///
203    /// let mut p = UnsignedPolynomial::<u8>::from_str("x^4+3*x^3+2*x+4").unwrap();
204    /// p.mod_nth_derivative_assign(2, 5);
205    /// assert_eq!(p.to_string(), "2*x^2+3*x");
206    ///
207    /// // 6 divides 3!.
208    /// let mut p = UnsignedPolynomial::<u8>::from_str("x^4+x^3").unwrap();
209    /// p.mod_nth_derivative_assign(3, 6);
210    /// assert_eq!(p.to_string(), "0");
211    /// ```
212    ///
213    /// FLINT has no `nmod_poly_nth_derivative`; this computes the same multipliers as
214    /// `fmpz_poly_nth_derivative` from `fmpz_poly/nth_derivative.c`, FLINT 3.6.0, directly modulo
215    /// `m`.
216    #[inline]
217    fn mod_nth_derivative_assign(&mut self, n: u64, m: T) {
218        mod_nth_derivative_in_place(self, n, m);
219    }
220}