Skip to main content

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}