labrador/core/
inner_product.rs

1use std::{
2    borrow::Borrow,
3    ops::{Add, Mul},
4};
5
6/// Computes the **linear combination** of two equally-sized slices, returning the
7/// sum of pair-wise products.
8///
9/// Conceptually, given two slices  
10/// `elements = [A1, A2, … , An]` and `challenges = [c1, c2, … , cn]`,  
11/// the function evaluates  
12///
13/// ```text
14/// Σ (Ai · ci)  for i = 1 .. n
15/// ```
16///
17/// where `·` is multiplication for the underlying types and `+` is the
18/// corresponding addition. The result’s type (`B`) is the same as the borrowed
19/// type of each element in `elements`.
20///
21/// # Arguments
22///
23/// * `elements`   – Slice of items whose borrowed values will be multiplied by
24///   the corresponding entries in `challenges`.
25/// * `challenges` – Slice of multipliers—**must** be the same length as
26///   `elements`.
27///
28/// # Returns
29///
30/// The accumulated sum **Σ `(elements[i] * challenges[i])`** as a value of type
31/// `B`.
32///
33///
34/// # Examples
35///
36/// Basic usage with primitive integers:
37///
38/// ```rust
39/// use std::borrow::Borrow;
40/// use rand::rng;
41/// use labrador::ring::rq::Rq;
42/// use labrador::core::inner_product::compute_linear_combination;
43///
44/// // The function is generic; we can call it directly.
45/// let elems = [Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng())];
46/// let challenges = [Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng()), Rq::random(&mut rng())];
47/// let sum = compute_linear_combination(&elems, &challenges);
48/// ```
49///
50pub fn compute_linear_combination<E, B, C>(elements: &[E], challenges: &[C]) -> B
51where
52    E: Borrow<B>,
53    for<'a> &'a B: Mul<&'a C, Output = B>,
54    for<'a> &'a B: Add<&'a B, Output = B>,
55{
56    debug_assert_eq!(
57        elements.len(),
58        challenges.len(),
59        "vectors must be the same length"
60    );
61    debug_assert!(!elements.is_empty(), "`elements` must not be empty");
62
63    let mut zipped_iter = elements.iter().zip(challenges.iter());
64    // Must do the following as the init value in fold requires size of B
65    let (e0, c0) = zipped_iter.next().unwrap();
66    let init = e0.borrow() * c0;
67
68    zipped_iter.fold(init, |acc, (elem, c)| &acc + &(elem.borrow() * c))
69}
70
71#[cfg(test)]
72mod tests {
73    use rand::rng;
74
75    use crate::ring::{rq::Rq, rq_matrix::RqMatrix, rq_vector::RqVector, zq::Zq};
76
77    use super::*;
78
79    #[test]
80    #[should_panic]
81    fn test_inputs_with_different_lengths_panic() {
82        let vector_a = RqVector::random(&mut rng(), 100);
83        let vector_b = RqVector::random(&mut rng(), 90);
84        let _ = compute_linear_combination(vector_a.get_elements(), vector_b.get_elements());
85    }
86
87    #[test]
88    fn test_zq_type_inputs() {
89        let poly_a = Rq::random(&mut rng());
90        let poly_b = Rq::random(&mut rng());
91
92        let result =
93            compute_linear_combination(poly_a.get_coefficients(), poly_b.get_coefficients());
94
95        let mut expected = Zq::ZERO;
96        for i in 0..poly_a.get_coefficients().len() {
97            expected += poly_a.get_coefficients()[i] * poly_b.get_coefficients()[i];
98        }
99        assert_eq!(result, expected);
100    }
101
102    #[test]
103    fn test_rq_type_inputs() {
104        let poly_vec_a = RqVector::random(&mut rng(), 100);
105        let poly_vec_b = RqVector::random(&mut rng(), 100);
106
107        let result =
108            compute_linear_combination(poly_vec_a.get_elements(), poly_vec_b.get_elements());
109
110        let mut expected = Rq::zero();
111        for i in 0..poly_vec_a.get_elements().len() {
112            expected = &expected + &(&poly_vec_a.get_elements()[i] * &poly_vec_b.get_elements()[i]);
113        }
114        assert_eq!(result, expected);
115    }
116
117    #[test]
118    fn test_zq_rq_type_inputs() {
119        let poly_a = Rq::random(&mut rng());
120        let poly_vec_b = RqVector::random(&mut rng(), 64);
121
122        let result =
123            compute_linear_combination(poly_vec_b.get_elements(), poly_a.get_coefficients());
124
125        let mut expected = Rq::zero();
126        for i in 0..poly_a.get_coefficients().len() {
127            expected = &expected + &(&poly_vec_b.get_elements()[i] * &poly_a.get_coefficients()[i]);
128        }
129        assert_eq!(result, expected);
130    }
131
132    #[test]
133    fn test_zq_rqvector_type_inputs() {
134        let poly_a = Rq::random(&mut rng());
135        let poly_vec_b = RqMatrix::random(&mut rng(), 64, 100);
136
137        let result =
138            compute_linear_combination(poly_vec_b.get_elements(), poly_a.get_coefficients());
139
140        let mut expected = RqVector::zero(100);
141        for i in 0..poly_a.get_coefficients().len() {
142            expected = &expected + &(&poly_vec_b.get_elements()[i] * poly_a.get_coefficients()[i]);
143        }
144        assert_eq!(result, expected);
145    }
146
147    #[test]
148    fn test_rq_rqvector_type_inputs() {
149        let poly_vec_a = RqVector::random(&mut rng(), 80);
150        let poly_vec_b = RqMatrix::random(&mut rng(), 80, 100);
151
152        let result =
153            compute_linear_combination(poly_vec_b.get_elements(), poly_vec_a.get_elements());
154
155        let mut expected = RqVector::zero(100);
156        for i in 0..poly_vec_a.get_elements().len() {
157            expected = &expected + &(&poly_vec_b.get_elements()[i] * &poly_vec_a.get_elements()[i]);
158        }
159        assert_eq!(result, expected);
160    }
161}