la_stack/
vector.rs

1//! Fixed-size, stack-allocated vectors.
2
3/// Fixed-size vector of length `D`, stored inline.
4#[must_use]
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Vector<const D: usize> {
7    pub(crate) data: [f64; D],
8}
9
10impl<const D: usize> Vector<D> {
11    /// Create a vector from a backing array.
12    ///
13    /// # Examples
14    /// ```
15    /// #![allow(unused_imports)]
16    /// use la_stack::prelude::*;
17    ///
18    /// let v = Vector::<3>::new([1.0, 2.0, 3.0]);
19    /// assert_eq!(v.into_array(), [1.0, 2.0, 3.0]);
20    /// ```
21    #[inline]
22    pub const fn new(data: [f64; D]) -> Self {
23        Self { data }
24    }
25
26    /// All-zeros vector.
27    ///
28    /// # Examples
29    /// ```
30    /// #![allow(unused_imports)]
31    /// use la_stack::prelude::*;
32    ///
33    /// let z = Vector::<2>::zero();
34    /// assert_eq!(z.into_array(), [0.0, 0.0]);
35    /// ```
36    #[inline]
37    pub const fn zero() -> Self {
38        Self { data: [0.0; D] }
39    }
40
41    /// Borrow the backing array.
42    ///
43    /// # Examples
44    /// ```
45    /// #![allow(unused_imports)]
46    /// use la_stack::prelude::*;
47    ///
48    /// let v = Vector::<2>::new([1.0, -2.0]);
49    /// assert_eq!(v.as_array(), &[1.0, -2.0]);
50    /// ```
51    #[inline]
52    #[must_use]
53    pub const fn as_array(&self) -> &[f64; D] {
54        &self.data
55    }
56
57    /// Consume and return the backing array.
58    ///
59    /// # Examples
60    /// ```
61    /// #![allow(unused_imports)]
62    /// use la_stack::prelude::*;
63    ///
64    /// let v = Vector::<2>::new([1.0, 2.0]);
65    /// let a = v.into_array();
66    /// assert_eq!(a, [1.0, 2.0]);
67    /// ```
68    #[inline]
69    #[must_use]
70    pub const fn into_array(self) -> [f64; D] {
71        self.data
72    }
73
74    /// Dot product.
75    ///
76    /// # Examples
77    /// ```
78    /// #![allow(unused_imports)]
79    /// use la_stack::prelude::*;
80    ///
81    /// let a = Vector::<3>::new([1.0, 2.0, 3.0]);
82    /// let b = Vector::<3>::new([-2.0, 0.5, 4.0]);
83    /// assert!((a.dot(b) - 11.0).abs() <= 1e-12);
84    /// ```
85    #[inline]
86    #[must_use]
87    pub fn dot(self, other: Self) -> f64 {
88        let mut acc = 0.0;
89        let mut i = 0;
90        while i < D {
91            acc = self.data[i].mul_add(other.data[i], acc);
92            i += 1;
93        }
94        acc
95    }
96
97    /// Squared Euclidean norm.
98    ///
99    /// # Examples
100    /// ```
101    /// #![allow(unused_imports)]
102    /// use la_stack::prelude::*;
103    ///
104    /// let v = Vector::<3>::new([1.0, 2.0, 3.0]);
105    /// assert!((v.norm2_sq() - 14.0).abs() <= 1e-12);
106    /// ```
107    #[inline]
108    #[must_use]
109    pub fn norm2_sq(self) -> f64 {
110        self.dot(self)
111    }
112}
113
114impl<const D: usize> Default for Vector<D> {
115    #[inline]
116    fn default() -> Self {
117        Self::zero()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    use core::hint::black_box;
126
127    use approx::assert_abs_diff_eq;
128    use pastey::paste;
129
130    macro_rules! gen_public_api_vector_tests {
131        ($d:literal) => {
132            paste! {
133                #[test]
134                fn [<public_api_vector_new_as_array_into_array_ $d d>]() {
135                    let arr = {
136                        let mut arr = [0.0f64; $d];
137                        let values = [1.0f64, 2.0, 3.0, 4.0, 5.0];
138                        for (dst, src) in arr.iter_mut().zip(values.iter()) {
139                            *dst = *src;
140                        }
141                        arr
142                    };
143
144                    let v = Vector::<$d>::new(arr);
145
146                    for i in 0..$d {
147                        assert_abs_diff_eq!(v.as_array()[i], arr[i], epsilon = 0.0);
148                    }
149
150                    let out = v.into_array();
151                    for i in 0..$d {
152                        assert_abs_diff_eq!(out[i], arr[i], epsilon = 0.0);
153                    }
154                }
155
156                #[test]
157                fn [<public_api_vector_zero_as_array_into_array_default_ $d d>]() {
158                    let z = Vector::<$d>::zero();
159                    for &x in z.as_array() {
160                        assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
161                    }
162                    for x in z.into_array() {
163                        assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
164                    }
165
166                    let d = Vector::<$d>::default();
167                    for x in d.into_array() {
168                        assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
169                    }
170                }
171
172                #[test]
173                fn [<public_api_vector_dot_and_norm2_sq_ $d d>]() {
174                    // Use black_box to avoid constant-folding/inlining eliminating the actual dot loop,
175                    // which can make coverage tools report the mul_add line as uncovered.
176
177                    let a_arr = {
178                        let mut arr = [0.0f64; $d];
179                        let values = [1.0f64, 2.0, 3.0, 4.0, 5.0];
180                        for (dst, src) in arr.iter_mut().zip(values.iter()) {
181                            *dst = black_box(*src);
182                        }
183                        arr
184                    };
185                    let b_arr = {
186                        let mut arr = [0.0f64; $d];
187                        let values = [-2.0f64, 0.5, 4.0, -1.0, 2.0];
188                        for (dst, src) in arr.iter_mut().zip(values.iter()) {
189                            *dst = black_box(*src);
190                        }
191                        arr
192                    };
193
194                    let expected_dot = {
195                        let mut acc = 0.0;
196                        let mut i = 0;
197                        while i < $d {
198                            acc = a_arr[i].mul_add(b_arr[i], acc);
199                            i += 1;
200                        }
201                        acc
202                    };
203                    let expected_norm2_sq = {
204                        let mut acc = 0.0;
205                        let mut i = 0;
206                        while i < $d {
207                            acc = a_arr[i].mul_add(a_arr[i], acc);
208                            i += 1;
209                        }
210                        acc
211                    };
212
213                    let a = Vector::<$d>::new(black_box(a_arr));
214                    let b = Vector::<$d>::new(black_box(b_arr));
215
216                    // Call via (black_boxed) fn pointers to discourage inlining, improving line-level coverage
217                    // attribution for the loop body.
218                    let dot_fn: fn(Vector<$d>, Vector<$d>) -> f64 = black_box(Vector::<$d>::dot);
219                    let norm2_sq_fn: fn(Vector<$d>) -> f64 = black_box(Vector::<$d>::norm2_sq);
220
221                    assert_abs_diff_eq!(dot_fn(black_box(a), black_box(b)), expected_dot, epsilon = 1e-14);
222                    assert_abs_diff_eq!(norm2_sq_fn(black_box(a)), expected_norm2_sq, epsilon = 1e-14);
223                }
224            }
225        };
226    }
227
228    // Mirror delaunay-style multi-dimension tests.
229    gen_public_api_vector_tests!(2);
230    gen_public_api_vector_tests!(3);
231    gen_public_api_vector_tests!(4);
232    gen_public_api_vector_tests!(5);
233}