malachite_base/unsigned_polynomial/arithmetic/mod_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::polynomial::{ModDerivative, ModDerivativeAssign};
12use crate::unsigned_polynomial::UnsignedPolynomial;
13
14fn assert_reduced<T: PrimitiveUnsigned>(p: &UnsignedPolynomial<T>, m: T) {
15 assert!(
16 p.mod_is_reduced(&m),
17 "self must be reduced mod m, but {p} has a coefficient >= {m}"
18 );
19}
20
21// Multiplies the coefficient of x^i, for i at least 1, by i mod m, which is kept as a running
22// counter so that no index needs converting to `T`, and moves it to x^(i-1). The products can be
23// zero, so the result is trimmed.
24fn mod_derivative_ref<T: PrimitiveUnsigned>(
25 p: &UnsignedPolynomial<T>,
26 m: T,
27) -> UnsignedPolynomial<T> {
28 assert_reduced(p, m);
29 let mut i = T::ZERO;
30 let mut q = UnsignedPolynomial {
31 coefficients: p
32 .coefficients
33 .iter()
34 .skip(1)
35 .map(|&c| {
36 i += T::ONE;
37 if i == m {
38 i = T::ZERO;
39 }
40 c.mod_mul(i, m)
41 })
42 .collect(),
43 };
44 q.trim();
45 q
46}
47
48fn mod_derivative_in_place<T: PrimitiveUnsigned>(p: &mut UnsignedPolynomial<T>, m: T) {
49 assert_reduced(p, m);
50 if p.coefficients.is_empty() {
51 return;
52 }
53 p.coefficients.remove(0);
54 let mut i = T::ZERO;
55 for c in &mut p.coefficients {
56 i += T::ONE;
57 if i == m {
58 i = T::ZERO;
59 }
60 c.mod_mul_assign(i, m);
61 }
62 p.trim();
63}
64
65impl<T: PrimitiveUnsigned> ModDerivative<T> for UnsignedPolynomial<T> {
66 type Output = Self;
67
68 /// Computes the derivative of an [`UnsignedPolynomial`] modulo `m`, taking the polynomial by
69 /// value. The coefficients must already be reduced modulo `m`.
70 ///
71 /// $$
72 /// f(p, m) = p' \bmod m.
73 /// $$
74 ///
75 /// The coefficient of $x^i$ is multiplied by $i$, reduced, and moved to $x^{i-1}$. Since $ia_i$
76 /// can be divisible by the modulus even when $a_i$ is not zero, the derivative can lose any
77 /// number of degrees. A constant polynomial, including zero, has derivative zero.
78 ///
79 /// # Worst-case complexity
80 /// $T(n) = O(n)$
81 ///
82 /// $M(n) = O(n)$
83 ///
84 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
85 ///
86 /// # Panics
87 /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
88 ///
89 /// # Examples
90 /// ```
91 /// use core::str::FromStr;
92 /// use malachite_base::polynomial::ModDerivative;
93 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
94 ///
95 /// let p = UnsignedPolynomial::<u8>::from_str("x^3+3*x^2+2*x+5").unwrap();
96 /// assert_eq!(p.mod_derivative(6).to_string(), "3*x^2+2");
97 ///
98 /// // The derivative can lose more than one degree.
99 /// let p = UnsignedPolynomial::<u8>::from_str("x^3+2*x+1").unwrap();
100 /// assert_eq!(p.mod_derivative(3).to_string(), "2");
101 /// ```
102 ///
103 /// This is equivalent to `nmod_poly_derivative` from `nmod_poly/derivative.c`, FLINT 3.6.0.
104 #[inline]
105 fn mod_derivative(mut self, m: T) -> Self {
106 mod_derivative_in_place(&mut self, m);
107 self
108 }
109}
110
111impl<T: PrimitiveUnsigned> ModDerivative<T> for &UnsignedPolynomial<T> {
112 type Output = UnsignedPolynomial<T>;
113
114 /// Computes the derivative of an [`UnsignedPolynomial`] modulo `m`, taking the polynomial by
115 /// reference. The coefficients must already be reduced modulo `m`.
116 ///
117 /// $$
118 /// f(p, m) = p' \bmod m.
119 /// $$
120 ///
121 /// The coefficient of $x^i$ is multiplied by $i$, reduced, and moved to $x^{i-1}$. Since $ia_i$
122 /// can be divisible by the modulus even when $a_i$ is not zero, the derivative can lose any
123 /// number of degrees. A constant polynomial, including zero, has derivative zero.
124 ///
125 /// # Worst-case complexity
126 /// $T(n) = O(n)$
127 ///
128 /// $M(n) = O(n)$
129 ///
130 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
131 ///
132 /// # Panics
133 /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
134 ///
135 /// # Examples
136 /// ```
137 /// use core::str::FromStr;
138 /// use malachite_base::polynomial::ModDerivative;
139 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
140 ///
141 /// let p = UnsignedPolynomial::<u8>::from_str("x^3+3*x^2+2*x+5").unwrap();
142 /// assert_eq!((&p).mod_derivative(6).to_string(), "3*x^2+2");
143 ///
144 /// // The derivative can lose more than one degree.
145 /// let p = UnsignedPolynomial::<u8>::from_str("x^3+2*x+1").unwrap();
146 /// assert_eq!((&p).mod_derivative(3).to_string(), "2");
147 /// ```
148 ///
149 /// This is equivalent to `nmod_poly_derivative` from `nmod_poly/derivative.c`, FLINT 3.6.0.
150 #[inline]
151 fn mod_derivative(self, m: T) -> UnsignedPolynomial<T> {
152 mod_derivative_ref(self, m)
153 }
154}
155
156impl<T: PrimitiveUnsigned> ModDerivativeAssign<T> for UnsignedPolynomial<T> {
157 /// Replaces an [`UnsignedPolynomial`] with its derivative modulo `m`, in place. The
158 /// coefficients must already be reduced modulo `m`.
159 ///
160 /// $$
161 /// p \gets p' \bmod m.
162 /// $$
163 ///
164 /// The coefficient of $x^i$ is multiplied by $i$, reduced, and moved to $x^{i-1}$. Since $ia_i$
165 /// can be divisible by the modulus even when $a_i$ is not zero, the derivative can lose any
166 /// number of degrees. A constant polynomial, including zero, has derivative zero.
167 ///
168 /// # Worst-case complexity
169 /// $T(n) = O(n)$
170 ///
171 /// $M(n) = O(n)$
172 ///
173 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.len()`.
174 ///
175 /// # Panics
176 /// Panics if `m` is 0, or if any coefficient of `self` is greater than or equal to `m`.
177 ///
178 /// # Examples
179 /// ```
180 /// use core::str::FromStr;
181 /// use malachite_base::polynomial::ModDerivativeAssign;
182 /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
183 ///
184 /// let mut p = UnsignedPolynomial::<u8>::from_str("x^3+3*x^2+2*x+5").unwrap();
185 /// p.mod_derivative_assign(6);
186 /// assert_eq!(p.to_string(), "3*x^2+2");
187 ///
188 /// // The derivative can lose more than one degree.
189 /// let mut p = UnsignedPolynomial::<u8>::from_str("x^3+2*x+1").unwrap();
190 /// p.mod_derivative_assign(3);
191 /// assert_eq!(p.to_string(), "2");
192 /// ```
193 ///
194 /// This is equivalent to `nmod_poly_derivative` from `nmod_poly/derivative.c`, FLINT 3.6.0.
195 #[inline]
196 fn mod_derivative_assign(&mut self, m: T) {
197 mod_derivative_in_place(self, m);
198 }
199}