Skip to main content

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}