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