Skip to main content

malachite_base/unsigned_polynomial/arithmetic/
mod_mul_truncated.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/>.
8use crate::num::basic::traits::Zero;
9use crate::num::basic::unsigneds::PrimitiveUnsigned;
10use crate::polynomial::{ModMulTruncated, ModMulTruncatedAssign};
11use crate::unsigned_polynomial::UnsignedPolynomial;
12use crate::unsigned_polynomial::arithmetic::mod_add::assert_reduced;
13use crate::unsigned_polynomial::arithmetic::mod_mul::{
14    MOD_MUL_KARATSUBA_THRESHOLD, ModData, column_sum, mod_add_assign_slice, mod_mul_karatsuba,
15};
16use crate::unsigned_polynomial::arithmetic::mod_power_of_2_mul::from_coefficients_trimmed;
17use crate::unsigned_polynomial::arithmetic::mod_power_of_2_mul_truncated::truncated_len;
18use alloc::vec;
19use core::cmp::min;
20
21// Sets `out` to the first `out.len()` coefficients of the product of the polynomials with
22// coefficients `xs` and `ys`, both nonempty, reduced modulo $m$, by schoolbook multiplication, one
23// coefficient at a time.
24pub(crate) fn mod_mul_truncated_classical<T: PrimitiveUnsigned>(
25    out: &mut [T],
26    xs: &[T],
27    ys: &[T],
28    d: &ModData<T>,
29) {
30    let n = xs.len();
31    let m = ys.len();
32    for (k, o) in out.iter_mut().enumerate() {
33        if k > n + m - 2 {
34            *o = T::ZERO;
35            continue;
36        }
37        let start = k.saturating_sub(m - 1);
38        let stop = min(k, n - 1);
39        let acc = column_sum(&xs[start..=stop], &ys[k - stop..=k - start], d);
40        *o = d.reduce_sum(acc);
41    }
42}
43
44// Sets `out` to the first `out.len()` coefficients of the product of the polynomials with
45// coefficients `xs` and `ys`, both nonempty, modulo $m$. With $n$ equal to `out.len()` and $h =
46// \lceil n/2 \rceil$, write x = x_0 + x^h x_1 and y = y_0 + x^h y_1; since $2h \geq n$, xy mod x^n
47// is x_0 y_0 + x^h (x_1 y_0 + x_0 y_1) mod x^n. The first product is a full product of half-length
48// factors, computed by Karatsuba multiplication, and the other two are truncated products of half
49// the length, computed recursively.
50pub(crate) fn mod_mul_truncated_karatsuba<T: PrimitiveUnsigned>(
51    out: &mut [T],
52    xs: &[T],
53    ys: &[T],
54    d: &ModData<T>,
55) {
56    let len = out.len();
57    let xs = &xs[..min(xs.len(), len)];
58    let ys = &ys[..min(ys.len(), len)];
59    let (xs, ys) = if xs.len() >= ys.len() {
60        (xs, ys)
61    } else {
62        (ys, xs)
63    };
64    let full_len = xs.len() + ys.len() - 1;
65    if full_len <= len {
66        mod_mul_karatsuba(&mut out[..full_len], xs, ys, d);
67        out[full_len..].fill(T::ZERO);
68        return;
69    }
70    if ys.len() < MOD_MUL_KARATSUBA_THRESHOLD {
71        mod_mul_truncated_classical(out, xs, ys, d);
72        return;
73    }
74    let h = len.div_ceil(2);
75    let x0 = &xs[..min(h, xs.len())];
76    let y0 = &ys[..min(h, ys.len())];
77    // The product of x_0 and y_0 has at most 2h - 1 <= `len` coefficients, so this is a full
78    // product.
79    mod_mul_truncated_karatsuba(out, x0, y0, d);
80    let mut cross = vec![T::ZERO; len - h];
81    if xs.len() > h {
82        mod_mul_truncated_karatsuba(&mut cross, &xs[h..], y0, d);
83        mod_add_assign_slice(&mut out[h..], &cross, d.m);
84    }
85    if ys.len() > h {
86        mod_mul_truncated_karatsuba(&mut cross, x0, &ys[h..], d);
87        mod_add_assign_slice(&mut out[h..], &cross, d.m);
88    }
89}
90
91fn assert_lengths<T>(out: &[T], xs: &[T], ys: &[T]) {
92    assert!(!out.is_empty());
93    assert!(!xs.is_empty());
94    assert!(!ys.is_empty());
95}
96
97// Sets `out` to the first `out.len()` coefficients of the product of the polynomials with
98// coefficients `xs` and `ys`, all three nonempty and the inputs reduced modulo `m`, modulo `m`, by
99// schoolbook multiplication.
100crate_test_fn! {
101#[allow(dead_code)]
102mod_mul_truncated_to_out_classical<T: PrimitiveUnsigned>(
103    out: &mut [T],
104    xs: &[T],
105    ys: &[T],
106    m: T,
107) {
108    assert_lengths(out, xs, ys);
109    let terms = xs.len().min(ys.len()).min(out.len());
110    mod_mul_truncated_classical(out, xs, ys, &ModData::new(m, terms));
111}}
112
113// Sets `out` to the first `out.len()` coefficients of the product of the polynomials with
114// coefficients `xs` and `ys`, all three nonempty and the inputs reduced modulo `m`, modulo `m`, by
115// Karatsuba multiplication.
116crate_test_fn! {
117#[allow(dead_code)]
118mod_mul_truncated_to_out_karatsuba<T: PrimitiveUnsigned>(
119    out: &mut [T],
120    xs: &[T],
121    ys: &[T],
122    m: T,
123) {
124    assert_lengths(out, xs, ys);
125    let terms = xs.len().min(ys.len()).min(out.len());
126    mod_mul_truncated_karatsuba(out, xs, ys, &ModData::new(m, terms));
127}}
128
129/// Sets `out` to the first `out.len()` coefficients of the product of the polynomials with
130/// coefficients `xs` and `ys`, all three nonempty and the inputs reduced modulo `m`, modulo `m`.
131/// `m` must be positive.
132///
133/// This is not part of the public API; it is public so that `malachite-nz` can multiply
134/// `NaturalPolynomial`s modulo a word.
135#[doc(hidden)]
136pub fn mod_mul_truncated_to_out<T: PrimitiveUnsigned>(out: &mut [T], xs: &[T], ys: &[T], m: T) {
137    assert_lengths(out, xs, ys);
138    mod_mul_truncated_karatsuba(
139        out,
140        xs,
141        ys,
142        &ModData::new(m, xs.len().min(ys.len()).min(out.len())),
143    );
144}
145
146// The product of the polynomials with coefficients `xs` and `ys`, both reduced modulo `m`,
147// truncated to `len` coefficients and reduced modulo `m`.
148pub(crate) fn mod_mul_truncated_helper<T: PrimitiveUnsigned>(
149    xs: &[T],
150    ys: &[T],
151    len: u64,
152    m: T,
153) -> UnsignedPolynomial<T> {
154    if len == 0 || xs.is_empty() || ys.is_empty() {
155        return UnsignedPolynomial::ZERO;
156    }
157    let mut out = vec![T::ZERO; truncated_len(xs.len(), ys.len(), len)];
158    mod_mul_truncated_to_out(&mut out, xs, ys, m);
159    from_coefficients_trimmed(out)
160}
161
162impl<T: PrimitiveUnsigned> ModMulTruncated<Self, T> for UnsignedPolynomial<T> {
163    type Output = Self;
164
165    /// Multiplies two [`UnsignedPolynomial`]s modulo $m$, keeping only the coefficients of $x^i$
166    /// for $i$ less than `len`, taking both by value. The coefficients of both must already be
167    /// reduced modulo $m$.
168    ///
169    /// $$
170    /// f(p, q, n, m) = (pq \bmod x^n) \bmod m.
171    /// $$
172    ///
173    /// The polynomials need not already be truncated: this is the product of their images modulo
174    /// $x^n$, so only their first `len` coefficients are read.
175    ///
176    /// # Worst-case complexity
177    /// $T(n) = O(n^{\log_2 3})$
178    ///
179    /// $M(n) = O(n)$
180    ///
181    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
182    ///
183    /// # Panics
184    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
185    /// `m`.
186    ///
187    /// # Examples
188    /// ```
189    /// use core::str::FromStr;
190    /// use malachite_base::polynomial::ModMulTruncated;
191    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
192    ///
193    /// // The product is 2*x^3+11*x^2+19*x+10; its low two coefficients, modulo 7.
194    /// assert_eq!(
195    ///     UnsignedPolynomial::<u8>::from_str("x^2+3*x+2")
196    ///         .unwrap()
197    ///         .mod_mul_truncated(UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7)
198    ///         .to_string(),
199    ///     "5*x+3"
200    /// );
201    /// // The linear coefficient of the product, 7, vanishes modulo 7.
202    /// assert_eq!(
203    ///     UnsignedPolynomial::<u8>::from_str("x+6")
204    ///         .unwrap()
205    ///         .mod_mul_truncated(UnsignedPolynomial::<u8>::from_str("x+1").unwrap(), 2, 7)
206    ///         .to_string(),
207    ///     "6"
208    /// );
209    /// ```
210    ///
211    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
212    fn mod_mul_truncated(self, other: Self, len: u64, m: T) -> Self {
213        assert_reduced(&self, &other, m);
214        mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m)
215    }
216}
217
218impl<T: PrimitiveUnsigned> ModMulTruncated<&Self, T> for UnsignedPolynomial<T> {
219    type Output = Self;
220
221    /// Multiplies two [`UnsignedPolynomial`]s modulo $m$, keeping only the coefficients of $x^i$
222    /// for $i$ less than `len`, taking the first by value and the second by reference. The
223    /// coefficients of both must already be reduced modulo $m$.
224    ///
225    /// $$
226    /// f(p, q, n, m) = (pq \bmod x^n) \bmod m.
227    /// $$
228    ///
229    /// The polynomials need not already be truncated: this is the product of their images modulo
230    /// $x^n$, so only their first `len` coefficients are read.
231    ///
232    /// # Worst-case complexity
233    /// $T(n) = O(n^{\log_2 3})$
234    ///
235    /// $M(n) = O(n)$
236    ///
237    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
238    ///
239    /// # Panics
240    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
241    /// `m`.
242    ///
243    /// # Examples
244    /// ```
245    /// use core::str::FromStr;
246    /// use malachite_base::polynomial::ModMulTruncated;
247    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
248    ///
249    /// // The product is 2*x^3+11*x^2+19*x+10; its low two coefficients, modulo 7.
250    /// assert_eq!(
251    ///     UnsignedPolynomial::<u8>::from_str("x^2+3*x+2")
252    ///         .unwrap()
253    ///         .mod_mul_truncated(&UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7)
254    ///         .to_string(),
255    ///     "5*x+3"
256    /// );
257    /// // The linear coefficient of the product, 7, vanishes modulo 7.
258    /// assert_eq!(
259    ///     UnsignedPolynomial::<u8>::from_str("x+6")
260    ///         .unwrap()
261    ///         .mod_mul_truncated(&UnsignedPolynomial::<u8>::from_str("x+1").unwrap(), 2, 7)
262    ///         .to_string(),
263    ///     "6"
264    /// );
265    /// ```
266    ///
267    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
268    fn mod_mul_truncated(self, other: &Self, len: u64, m: T) -> Self {
269        assert_reduced(&self, other, m);
270        mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m)
271    }
272}
273
274impl<T: PrimitiveUnsigned> ModMulTruncated<UnsignedPolynomial<T>, T> for &UnsignedPolynomial<T> {
275    type Output = UnsignedPolynomial<T>;
276
277    /// Multiplies two [`UnsignedPolynomial`]s modulo $m$, keeping only the coefficients of $x^i$
278    /// for $i$ less than `len`, taking the first by reference and the second by value. The
279    /// coefficients of both must already be reduced modulo $m$.
280    ///
281    /// $$
282    /// f(p, q, n, m) = (pq \bmod x^n) \bmod m.
283    /// $$
284    ///
285    /// The polynomials need not already be truncated: this is the product of their images modulo
286    /// $x^n$, so only their first `len` coefficients are read.
287    ///
288    /// # Worst-case complexity
289    /// $T(n) = O(n^{\log_2 3})$
290    ///
291    /// $M(n) = O(n)$
292    ///
293    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
294    ///
295    /// # Panics
296    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
297    /// `m`.
298    ///
299    /// # Examples
300    /// ```
301    /// use core::str::FromStr;
302    /// use malachite_base::polynomial::ModMulTruncated;
303    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
304    ///
305    /// // The product is 2*x^3+11*x^2+19*x+10; its low two coefficients, modulo 7.
306    /// assert_eq!(
307    ///     (&UnsignedPolynomial::<u8>::from_str("x^2+3*x+2").unwrap())
308    ///         .mod_mul_truncated(UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7)
309    ///         .to_string(),
310    ///     "5*x+3"
311    /// );
312    /// // The linear coefficient of the product, 7, vanishes modulo 7.
313    /// assert_eq!(
314    ///     (&UnsignedPolynomial::<u8>::from_str("x+6").unwrap())
315    ///         .mod_mul_truncated(UnsignedPolynomial::<u8>::from_str("x+1").unwrap(), 2, 7)
316    ///         .to_string(),
317    ///     "6"
318    /// );
319    /// ```
320    ///
321    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
322    fn mod_mul_truncated(
323        self,
324        other: UnsignedPolynomial<T>,
325        len: u64,
326        m: T,
327    ) -> UnsignedPolynomial<T> {
328        assert_reduced(self, &other, m);
329        mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m)
330    }
331}
332
333impl<T: PrimitiveUnsigned> ModMulTruncated<&UnsignedPolynomial<T>, T> for &UnsignedPolynomial<T> {
334    type Output = UnsignedPolynomial<T>;
335
336    /// Multiplies two [`UnsignedPolynomial`]s modulo $m$, keeping only the coefficients of $x^i$
337    /// for $i$ less than `len`, taking both by reference. The coefficients of both must already be
338    /// reduced modulo $m$.
339    ///
340    /// $$
341    /// f(p, q, n, m) = (pq \bmod x^n) \bmod m.
342    /// $$
343    ///
344    /// The polynomials need not already be truncated: this is the product of their images modulo
345    /// $x^n$, so only their first `len` coefficients are read.
346    ///
347    /// # Worst-case complexity
348    /// $T(n) = O(n^{\log_2 3})$
349    ///
350    /// $M(n) = O(n)$
351    ///
352    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
353    ///
354    /// # Panics
355    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
356    /// `m`.
357    ///
358    /// # Examples
359    /// ```
360    /// use core::str::FromStr;
361    /// use malachite_base::polynomial::ModMulTruncated;
362    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
363    ///
364    /// // The product is 2*x^3+11*x^2+19*x+10; its low two coefficients, modulo 7.
365    /// assert_eq!(
366    ///     (&UnsignedPolynomial::<u8>::from_str("x^2+3*x+2").unwrap())
367    ///         .mod_mul_truncated(&UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7)
368    ///         .to_string(),
369    ///     "5*x+3"
370    /// );
371    /// // The linear coefficient of the product, 7, vanishes modulo 7.
372    /// assert_eq!(
373    ///     (&UnsignedPolynomial::<u8>::from_str("x+6").unwrap())
374    ///         .mod_mul_truncated(&UnsignedPolynomial::<u8>::from_str("x+1").unwrap(), 2, 7)
375    ///         .to_string(),
376    ///     "6"
377    /// );
378    /// ```
379    ///
380    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
381    fn mod_mul_truncated(
382        self,
383        other: &UnsignedPolynomial<T>,
384        len: u64,
385        m: T,
386    ) -> UnsignedPolynomial<T> {
387        assert_reduced(self, other, m);
388        mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m)
389    }
390}
391
392impl<T: PrimitiveUnsigned> ModMulTruncatedAssign<Self, T> for UnsignedPolynomial<T> {
393    /// Multiplies an [`UnsignedPolynomial`] by another [`UnsignedPolynomial`] modulo $m$ in place,
394    /// keeping only the coefficients of $x^i$ for $i$ less than `len`, taking the right-hand side
395    /// by value. The coefficients of both must already be reduced modulo $m$.
396    ///
397    /// $$
398    /// p \gets (pq \bmod x^n) \bmod m.
399    /// $$
400    ///
401    /// # Worst-case complexity
402    /// $T(n) = O(n^{\log_2 3})$
403    ///
404    /// $M(n) = O(n)$
405    ///
406    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
407    ///
408    /// # Panics
409    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
410    /// `m`.
411    ///
412    /// # Examples
413    /// ```
414    /// use core::str::FromStr;
415    /// use malachite_base::polynomial::ModMulTruncatedAssign;
416    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
417    ///
418    /// let mut p = UnsignedPolynomial::<u8>::from_str("x^2+3*x+2").unwrap();
419    /// p.mod_mul_truncated_assign(UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7);
420    /// assert_eq!(p.to_string(), "5*x+3");
421    /// ```
422    ///
423    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
424    fn mod_mul_truncated_assign(&mut self, other: Self, len: u64, m: T) {
425        assert_reduced(self, &other, m);
426        *self = mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m);
427    }
428}
429
430impl<T: PrimitiveUnsigned> ModMulTruncatedAssign<&Self, T> for UnsignedPolynomial<T> {
431    /// Multiplies an [`UnsignedPolynomial`] by another [`UnsignedPolynomial`] modulo $m$ in place,
432    /// keeping only the coefficients of $x^i$ for $i$ less than `len`, taking the right-hand side
433    /// by reference. The coefficients of both must already be reduced modulo $m$.
434    ///
435    /// $$
436    /// p \gets (pq \bmod x^n) \bmod m.
437    /// $$
438    ///
439    /// # Worst-case complexity
440    /// $T(n) = O(n^{\log_2 3})$
441    ///
442    /// $M(n) = O(n)$
443    ///
444    /// where $T$ is time, $M$ is additional memory, and $n$ is `len`.
445    ///
446    /// # Panics
447    /// Panics if `m` is 0, or if any coefficient of `self` or `other` is greater than or equal to
448    /// `m`.
449    ///
450    /// # Examples
451    /// ```
452    /// use core::str::FromStr;
453    /// use malachite_base::polynomial::ModMulTruncatedAssign;
454    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
455    ///
456    /// let mut p = UnsignedPolynomial::<u8>::from_str("x^2+3*x+2").unwrap();
457    /// p.mod_mul_truncated_assign(&UnsignedPolynomial::<u8>::from_str("2*x+5").unwrap(), 2, 7);
458    /// assert_eq!(p.to_string(), "5*x+3");
459    /// ```
460    ///
461    /// This is equivalent to `nmod_poly_mullow` from `nmod_poly/mullow.c`, FLINT 3.6.0.
462    fn mod_mul_truncated_assign(&mut self, other: &Self, len: u64, m: T) {
463        assert_reduced(self, other, m);
464        *self = mod_mul_truncated_helper(&self.coefficients, &other.coefficients, len, m);
465    }
466}