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}