malachite_base/unsigned_vector/arithmetic/mod_dot.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::mod_mul::{limbs_invert_limb_u64, mod_preinverted_double};
10use crate::num::arithmetic::traits::{ModDot, ModIsReduced};
11use crate::num::basic::integers::PrimitiveInt;
12use crate::num::basic::unsigneds::PrimitiveUnsigned;
13use crate::num::logic::traits::LeadingZeros;
14use crate::unsigned_vector::UnsignedVector;
15
16// Dot products of vectors whose elements are words reduced modulo a word $m$. As in FLINT's
17// `nmod_vec` dot products, the sum of products is accumulated exactly, in one, two, or three words,
18// depending on how large it can get, and reduced once.
19
20// What summing products of vector elements and reducing the sums modulo $m$ needs: $m$, data for
21// reducing two-word values modulo $m$, and the number of words in which the sums are accumulated.
22// As in FLINT's `_nmod_vec_dot_bound_limbs`, a sum of at most `terms` products of values less than
23// $m$ is bounded by `terms` $(m - 1)^2$, and when the bound fits in one or two words, fewer words
24// are used.
25pub(crate) struct ModData<T: PrimitiveUnsigned> {
26 pub(crate) m: T,
27 // When `T` has 64 bits: the inverse of $m$, shifted left until its top bit is set, for
28 // `mod_preinverted_double`.
29 inv: u64,
30 // When `T` has more than 64 bits: $2^\text{W} \bmod m$, where W is `T::WIDTH`.
31 radix: T,
32 // 1, 2, or 3.
33 pub(crate) words: u8,
34 // When three words are used: the number of products that can be added to a reduced value
35 // without overflowing three words, with room to double the sum: $2^{\text{W} - 1}$, or
36 // `usize::MAX` if that does not fit.
37 pub(crate) max_terms: usize,
38}
39
40impl<T: PrimitiveUnsigned> ModData<T> {
41 // Sums will have at most `terms` products, which must be positive.
42 pub(crate) fn new(m: T, terms: usize) -> Self {
43 assert_ne!(m, T::ZERO);
44 let words = match T::try_from(terms) {
45 Ok(terms) => {
46 // (m - 1)^2 terms, in three words.
47 let (hi, lo) = T::x_mul_y_to_zz(m - T::ONE, m - T::ONE);
48 let carry = T::x_mul_y_to_zz(lo, terms).0;
49 let (top, mid) = T::x_mul_y_to_zz(hi, terms);
50 let (top, mid) = T::xx_add_yy_to_zz(top, mid, T::ZERO, carry);
51 if top != T::ZERO {
52 3
53 } else if mid != T::ZERO {
54 2
55 } else {
56 1
57 }
58 }
59 Err(_) => 3,
60 };
61 let inv = if T::WIDTH == u64::WIDTH {
62 let m: u64 = m.wrapping_into();
63 limbs_invert_limb_u64(m << LeadingZeros::leading_zeros(m))
64 } else {
65 0
66 };
67 Self {
68 m,
69 inv,
70 // 2^W mod m = (2^W - m) mod m, computed without overflow.
71 radix: if T::WIDTH > u64::WIDTH {
72 m.wrapping_neg() % m
73 } else {
74 T::ZERO
75 },
76 words,
77 max_terms: if T::WIDTH > usize::WIDTH {
78 usize::MAX
79 } else {
80 1 << (T::WIDTH - 1)
81 },
82 }
83 }
84
85 // The value $x_1 2^\text{W} + x_0$ modulo $m$. Words of at most 32 bits are combined into a
86 // `u64` and divided once, and 64-bit words are reduced with a precomputed inverse, as by
87 // FLINT's `NMOD2_RED2`.
88 #[inline]
89 pub(crate) fn reduce_2(&self, x1: T, x0: T) -> T {
90 let m = self.m;
91 if T::WIDTH <= u32::WIDTH {
92 let x: u64 = x1.wrapping_into();
93 let x0: u64 = x0.wrapping_into();
94 let m: u64 = m.wrapping_into();
95 T::wrapping_from(((x << T::WIDTH) | x0) % m)
96 } else if T::WIDTH == u64::WIDTH {
97 T::wrapping_from(mod_preinverted_double::<u64, u128>(
98 x1.wrapping_into(),
99 x0.wrapping_into(),
100 m.wrapping_into(),
101 self.inv,
102 ))
103 } else {
104 let data = T::precompute_mod_mul_data(&m);
105 (x1 % m)
106 .mod_mul_precomputed(self.radix, m, &data)
107 .mod_add(x0 % m, m)
108 }
109 }
110
111 // The value $x_2 2^{2\text{W}} + x_1 2^\text{W} + x_0$ modulo $m$, by Horner's rule, as by
112 // FLINT's `NMOD_RED3`.
113 #[inline]
114 pub(crate) fn reduce(&self, x2: T, x1: T, x0: T) -> T {
115 self.reduce_2(self.reduce_2(x2, x1), x0)
116 }
117
118 // A sum from `column_sum`, modulo $m$.
119 #[inline]
120 pub(crate) fn reduce_sum(&self, (x2, x1, x0): (T, T, T)) -> T {
121 match self.words {
122 1 => x0 % self.m,
123 2 => self.reduce_2(x1, x0),
124 _ => self.reduce(x2, x1, x0),
125 }
126 }
127}
128
129// Adds the product of `x` and `y` to the three-word accumulator `(a2, a1, a0)`.
130#[inline]
131pub(crate) fn accumulate<T: PrimitiveUnsigned>(acc: &mut (T, T, T), x: T, y: T) {
132 let (hi, lo) = T::x_mul_y_to_zz(x, y);
133 let (a2, a1, a0) = *acc;
134 *acc = T::xxx_add_yyy_to_zzz(a2, a1, a0, T::ZERO, hi, lo);
135}
136
137// The sum of the products of `xs[i]` and `ys[ys.len() - 1 - i]`, for `i` less than `xs.len()`, as a
138// three-word value, accumulated in `d.words` words; `xs` and `ys` must have the same length, at
139// most the number of terms `d` was made for. When three words are used, whenever the number of
140// products would exceed `d.max_terms` the sum so far is reduced modulo $m$, so the result is
141// congruent to the true sum and, with room to spare, can be doubled without overflowing.
142#[inline]
143pub(crate) fn column_sum<T: PrimitiveUnsigned>(xs: &[T], ys: &[T], d: &ModData<T>) -> (T, T, T) {
144 match d.words {
145 1 => {
146 let mut sum = T::ZERO;
147 for (&x, &y) in xs.iter().zip(ys.iter().rev()) {
148 sum.wrapping_add_assign(x.wrapping_mul(y));
149 }
150 (T::ZERO, T::ZERO, sum)
151 }
152 2 => {
153 let (mut hi, mut lo) = (T::ZERO, T::ZERO);
154 for (&x, &y) in xs.iter().zip(ys.iter().rev()) {
155 let (p_hi, p_lo) = T::x_mul_y_to_zz(x, y);
156 (hi, lo) = T::xx_add_yy_to_zz(hi, lo, p_hi, p_lo);
157 }
158 (T::ZERO, hi, lo)
159 }
160 _ => {
161 let mut acc = (T::ZERO, T::ZERO, T::ZERO);
162 for (i, (x_chunk, y_chunk)) in xs
163 .chunks(d.max_terms)
164 .zip(ys.rchunks(d.max_terms))
165 .enumerate()
166 {
167 if i != 0 {
168 acc = (T::ZERO, T::ZERO, d.reduce(acc.0, acc.1, acc.2));
169 }
170 for (&x, &y) in x_chunk.iter().zip(y_chunk.iter().rev()) {
171 accumulate(&mut acc, x, y);
172 }
173 }
174 acc
175 }
176 }
177}
178
179// The sum of the products of `xs[i]` and `ys[i]`, as a three-word value: `column_sum` without the
180// reversal of `ys`.
181#[inline]
182pub(crate) fn dot_sum<T: PrimitiveUnsigned>(xs: &[T], ys: &[T], d: &ModData<T>) -> (T, T, T) {
183 match d.words {
184 1 => {
185 let mut sum = T::ZERO;
186 for (&x, &y) in xs.iter().zip(ys) {
187 sum.wrapping_add_assign(x.wrapping_mul(y));
188 }
189 (T::ZERO, T::ZERO, sum)
190 }
191 2 => {
192 let (mut hi, mut lo) = (T::ZERO, T::ZERO);
193 for (&x, &y) in xs.iter().zip(ys) {
194 let (p_hi, p_lo) = T::x_mul_y_to_zz(x, y);
195 (hi, lo) = T::xx_add_yy_to_zz(hi, lo, p_hi, p_lo);
196 }
197 (T::ZERO, hi, lo)
198 }
199 _ => {
200 let mut acc = (T::ZERO, T::ZERO, T::ZERO);
201 for (i, (x_chunk, y_chunk)) in xs
202 .chunks(d.max_terms)
203 .zip(ys.chunks(d.max_terms))
204 .enumerate()
205 {
206 if i != 0 {
207 acc = (T::ZERO, T::ZERO, d.reduce(acc.0, acc.1, acc.2));
208 }
209 for (&x, &y) in x_chunk.iter().zip(y_chunk) {
210 accumulate(&mut acc, x, y);
211 }
212 }
213 acc
214 }
215 }
216}
217
218// The dot product of `xs` and `ys` modulo `m`, which must be positive; `xs` and `ys` must have the
219// same length and be reduced. The products are summed exactly in one, two, or three words and the
220// sum is reduced once.
221//
222// This is equivalent to `_nmod_vec_dot` from `nmod_vec/dot.c`, FLINT 3.6.0.
223pub(crate) fn mod_dot<T: PrimitiveUnsigned>(xs: &[T], ys: &[T], m: T) -> T {
224 if xs.is_empty() {
225 return T::ZERO;
226 }
227 let d = ModData::new(m, xs.len());
228 d.reduce_sum(dot_sum(xs, ys, &d))
229}
230
231fn mod_dot_vectors<T: PrimitiveUnsigned>(v: &UnsignedVector<T>, w: &UnsignedVector<T>, m: T) -> T {
232 assert_ne!(m, T::ZERO, "m cannot be zero");
233 assert_eq!(
234 v.elements.len(),
235 w.elements.len(),
236 "the vectors must have the same dimension"
237 );
238 assert!(
239 v.mod_is_reduced(&m),
240 "self must be reduced mod m, but {v} has an element >= {m}"
241 );
242 assert!(
243 w.mod_is_reduced(&m),
244 "other must be reduced mod m, but {w} has an element >= {m}"
245 );
246 mod_dot(&v.elements, &w.elements, m)
247}
248
249impl<T: PrimitiveUnsigned> ModDot<Self, T> for UnsignedVector<T> {
250 type Output = T;
251
252 /// Computes the dot product of two [`UnsignedVector`]s modulo $m$, taking the first by value
253 /// and the second by value. The elements of both must already be reduced.
254 ///
255 /// The dot product of two 0-dimensional vectors is 0.
256 ///
257 /// $$
258 /// f(v, w, m) = \sum_{i=0}^{n-1} v_iw_i \bmod m,
259 /// $$
260 ///
261 /// where $n$ is the dimension of $v$ and $w$.
262 ///
263 /// # Worst-case complexity
264 /// $T(n) = O(n)$
265 ///
266 /// $M(n) = O(1)$
267 ///
268 /// where $T$ is time, $M$ is additional memory, and $n$ is the dimension of the vectors.
269 ///
270 /// # Panics
271 /// Panics if `m` is zero, if the vectors have different dimensions, or if any element is
272 /// greater than or equal to `m`.
273 ///
274 /// # Examples
275 /// ```
276 /// use core::str::FromStr;
277 /// use malachite_base::num::arithmetic::traits::ModDot;
278 /// use malachite_base::unsigned_vector::UnsignedVector;
279 ///
280 /// let v = UnsignedVector::<u8>::from_str("(1, 2, 3)").unwrap();
281 /// let w = UnsignedVector::<u8>::from_str("(4, 5, 7)").unwrap();
282 /// assert_eq!(v.clone().mod_dot(w.clone(), 10), 5);
283 /// ```
284 ///
285 /// This is equivalent to `_nmod_vec_dot` from `nmod_vec/dot.c`, FLINT 3.6.0.
286 #[inline]
287 fn mod_dot(self, other: Self, m: T) -> T {
288 mod_dot_vectors(&self, &other, m)
289 }
290}
291
292impl<T: PrimitiveUnsigned> ModDot<&Self, T> for UnsignedVector<T> {
293 type Output = T;
294
295 /// Computes the dot product of two [`UnsignedVector`]s modulo $m$, taking the first by value
296 /// and the second by reference. The elements of both must already be reduced.
297 ///
298 /// The dot product of two 0-dimensional vectors is 0.
299 ///
300 /// $$
301 /// f(v, w, m) = \sum_{i=0}^{n-1} v_iw_i \bmod m,
302 /// $$
303 ///
304 /// where $n$ is the dimension of $v$ and $w$.
305 ///
306 /// # Worst-case complexity
307 /// $T(n) = O(n)$
308 ///
309 /// $M(n) = O(1)$
310 ///
311 /// where $T$ is time, $M$ is additional memory, and $n$ is the dimension of the vectors.
312 ///
313 /// # Panics
314 /// Panics if `m` is zero, if the vectors have different dimensions, or if any element is
315 /// greater than or equal to `m`.
316 ///
317 /// # Examples
318 /// ```
319 /// use core::str::FromStr;
320 /// use malachite_base::num::arithmetic::traits::ModDot;
321 /// use malachite_base::unsigned_vector::UnsignedVector;
322 ///
323 /// let v = UnsignedVector::<u8>::from_str("(1, 2, 3)").unwrap();
324 /// let w = UnsignedVector::<u8>::from_str("(4, 5, 7)").unwrap();
325 /// assert_eq!(v.clone().mod_dot(&w, 10), 5);
326 /// ```
327 ///
328 /// This is equivalent to `_nmod_vec_dot` from `nmod_vec/dot.c`, FLINT 3.6.0.
329 #[inline]
330 fn mod_dot(self, other: &Self, m: T) -> T {
331 mod_dot_vectors(&self, other, m)
332 }
333}
334
335impl<T: PrimitiveUnsigned> ModDot<UnsignedVector<T>, T> for &UnsignedVector<T> {
336 type Output = T;
337
338 /// Computes the dot product of two [`UnsignedVector`]s modulo $m$, taking the first by
339 /// reference and the second by value. The elements of both must already be reduced.
340 ///
341 /// The dot product of two 0-dimensional vectors is 0.
342 ///
343 /// $$
344 /// f(v, w, m) = \sum_{i=0}^{n-1} v_iw_i \bmod m,
345 /// $$
346 ///
347 /// where $n$ is the dimension of $v$ and $w$.
348 ///
349 /// # Worst-case complexity
350 /// $T(n) = O(n)$
351 ///
352 /// $M(n) = O(1)$
353 ///
354 /// where $T$ is time, $M$ is additional memory, and $n$ is the dimension of the vectors.
355 ///
356 /// # Panics
357 /// Panics if `m` is zero, if the vectors have different dimensions, or if any element is
358 /// greater than or equal to `m`.
359 ///
360 /// # Examples
361 /// ```
362 /// use core::str::FromStr;
363 /// use malachite_base::num::arithmetic::traits::ModDot;
364 /// use malachite_base::unsigned_vector::UnsignedVector;
365 ///
366 /// let v = UnsignedVector::<u8>::from_str("(1, 2, 3)").unwrap();
367 /// let w = UnsignedVector::<u8>::from_str("(4, 5, 7)").unwrap();
368 /// assert_eq!((&v).mod_dot(w.clone(), 10), 5);
369 /// ```
370 ///
371 /// This is equivalent to `_nmod_vec_dot` from `nmod_vec/dot.c`, FLINT 3.6.0.
372 #[inline]
373 fn mod_dot(self, other: UnsignedVector<T>, m: T) -> T {
374 mod_dot_vectors(self, &other, m)
375 }
376}
377
378impl<T: PrimitiveUnsigned> ModDot<&UnsignedVector<T>, T> for &UnsignedVector<T> {
379 type Output = T;
380
381 /// Computes the dot product of two [`UnsignedVector`]s modulo $m$, taking the first by
382 /// reference and the second by reference. The elements of both must already be reduced.
383 ///
384 /// The dot product of two 0-dimensional vectors is 0.
385 ///
386 /// $$
387 /// f(v, w, m) = \sum_{i=0}^{n-1} v_iw_i \bmod m,
388 /// $$
389 ///
390 /// where $n$ is the dimension of $v$ and $w$.
391 ///
392 /// # Worst-case complexity
393 /// $T(n) = O(n)$
394 ///
395 /// $M(n) = O(1)$
396 ///
397 /// where $T$ is time, $M$ is additional memory, and $n$ is the dimension of the vectors.
398 ///
399 /// # Panics
400 /// Panics if `m` is zero, if the vectors have different dimensions, or if any element is
401 /// greater than or equal to `m`.
402 ///
403 /// # Examples
404 /// ```
405 /// use core::str::FromStr;
406 /// use malachite_base::num::arithmetic::traits::ModDot;
407 /// use malachite_base::unsigned_vector::UnsignedVector;
408 ///
409 /// let v = UnsignedVector::<u8>::from_str("(1, 2, 3)").unwrap();
410 /// let w = UnsignedVector::<u8>::from_str("(4, 5, 7)").unwrap();
411 /// assert_eq!((&v).mod_dot(&w, 10), 5);
412 /// ```
413 ///
414 /// This is equivalent to `_nmod_vec_dot` from `nmod_vec/dot.c`, FLINT 3.6.0.
415 #[inline]
416 fn mod_dot(self, other: &UnsignedVector<T>, m: T) -> T {
417 mod_dot_vectors(self, other, m)
418 }
419}