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}