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}