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}