Skip to main content

mathverse_core/
arrays.rs

1//! Const-generic fixed-size numeric arrays.
2//!
3//! A `no_std`-friendly, zero-dependency alternative to heap-backed vectors for
4//! embedded and fixed-size workloads. Backed by a plain `[T; N]`, all methods
5//! are `const`-compatible where possible and require only `core`.
6//!
7//! ```text
8//!            ┌───────────────────────────┐
9//!            │  Array<T, const N: usize> │
10//!            └───────────────────────────┘
11//!                  │ T: Num
12//!            ┌─────┴─────┐
13//!            │ dot/sum   │  elementwise via core ops
14//!            └───────────┘
15//!                  │ T: Real
16//!            ┌─────┴─────┐
17//!            │ l1/l2/∞   │  norms, normalize
18//!            └───────────┘
19//! ```
20
21use crate::traits::{Num, Real};
22use core::ops::{Add, Div, Mul, Sub};
23
24/// Fixed-size numeric vector backed by `[T; N]`.
25///
26/// Unlike a heap-backed `Vec`, the length is part of the type, so no allocation
27/// or bounds checks are needed and the whole value can live on the stack or in a
28/// static. Works in `no_std` environments.
29///
30/// # Example
31/// ```
32/// use mathverse_core::arrays::Array;
33///
34/// let a = Array::from([1.0f64, 2.0, 3.0]);
35/// let b = Array::ones();
36/// let c = a + b;
37/// assert_eq!(c, Array::from([2.0, 3.0, 4.0]));
38/// assert!((a.dot(&b) - 6.0).abs() < 1e-12);
39/// ```
40#[derive(Clone, Copy, PartialEq, Debug)]
41pub struct Array<T, const N: usize> {
42    data: [T; N],
43}
44
45impl<T: Num, const N: usize> Default for Array<T, N> {
46    fn default() -> Self {
47        Self::zeros()
48    }
49}
50
51impl<T: Num, const N: usize> Array<T, N> {
52    /// Construct from a raw array.
53    pub const fn new(data: [T; N]) -> Self {
54        Self { data }
55    }
56
57    /// Build from a mapping function `f(i)`.
58    ///
59    /// ```
60    /// use mathverse_core::arrays::Array;
61    /// let a = Array::from_fn(|i| i as f64 * 2.0);
62    /// assert_eq!(a, Array::from([0.0, 2.0, 4.0]));
63    /// ```
64    pub fn from_fn<F: FnMut(usize) -> T>(mut f: F) -> Self {
65        let mut data = [T::zero(); N];
66        for (i, slot) in data.iter_mut().enumerate() {
67            *slot = f(i);
68        }
69        Self { data }
70    }
71
72    /// All elements set to `T::zero()`.
73    ///
74    /// ```
75    /// use mathverse_core::arrays::Array;
76    /// assert_eq!(Array::<f64, 3>::zeros(), Array::from([0.0; 3]));
77    /// ```
78    pub fn zeros() -> Self {
79        Self::from_fn(|_| T::zero())
80    }
81
82    /// All elements set to `T::one()`.
83    pub fn ones() -> Self {
84        Self::from_fn(|_| T::one())
85    }
86
87    /// The backing slice (read-only).
88    pub fn as_slice(&self) -> &[T] {
89        &self.data
90    }
91
92    /// The backing mutable slice.
93    pub fn as_mut_slice(&mut self) -> &mut [T] {
94        &mut self.data
95    }
96
97    /// Get element `i` (panics if out of bounds, same as slice indexing).
98    pub fn get(&self, i: usize) -> T {
99        self.data[i]
100    }
101
102    /// Set element `i` in place.
103    pub fn set(&mut self, i: usize, value: T) {
104        self.data[i] = value;
105    }
106
107    /// Element at index `i` without panicking.
108    pub fn try_get(&self, i: usize) -> Option<T> {
109        self.data.get(i).copied()
110    }
111
112    /// Sum of all elements.
113    pub fn sum(&self) -> T {
114        let mut acc = T::zero();
115        for &x in &self.data {
116            acc = acc + x;
117        }
118        acc
119    }
120
121    /// Dot (scalar) product with another array of the same length.
122    ///
123    /// ```
124    /// use mathverse_core::arrays::Array;
125    /// let a = Array::from([1.0f64, 2.0, 3.0]);
126    /// let b = Array::from([4.0, 5.0, 6.0]);
127    /// assert!((a.dot(&b) - 32.0).abs() < 1e-12);
128    /// ```
129    pub fn dot(&self, other: &Self) -> T {
130        let mut acc = T::zero();
131        for i in 0..N {
132            acc = acc + self.data[i] * other.data[i];
133        }
134        acc
135    }
136
137    /// Element-wise apply of `f`.
138    pub fn map<U: Num, F: FnMut(T) -> U>(&self, mut f: F) -> Array<U, N> {
139        Array::from_fn(|i| f(self.data[i]))
140    }
141
142    /// Zip with another array and combine with `f`.
143    pub fn zip_with<U: Num, V: Num, F: FnMut(T, U) -> V>(
144        &self,
145        other: &Array<U, N>,
146        mut f: F,
147    ) -> Array<V, N> {
148        Array::from_fn(|i| f(self.data[i], other.data[i]))
149    }
150
151    /// Element-wise `a * b`.
152    pub fn component_mul(&self, other: &Self) -> Self {
153        self.zip_with(other, |x, y| x * y)
154    }
155
156    /// Element-wise `a / b`.
157    pub fn component_div(&self, other: &Self) -> Self
158    where
159        T: Div<Output = T>,
160    {
161        self.zip_with(other, |x, y| x / y)
162    }
163
164    /// Cumulative sum (prefix sum).
165    ///
166    /// ```
167    /// use mathverse_core::arrays::Array;
168    /// let a = Array::from([1.0f64, 2.0, 3.0]);
169    /// assert_eq!(a.cumulative_sum(), Array::from([1.0, 3.0, 6.0]));
170    /// ```
171    pub fn cumulative_sum(&self) -> Self {
172        let mut acc = T::zero();
173        let mut out = [T::zero(); N];
174        for (i, slot) in out.iter_mut().enumerate() {
175            acc = acc + self.data[i];
176            *slot = acc;
177        }
178        Self { data: out }
179    }
180
181    /// Reverse the elements.
182    pub fn reversed(&self) -> Self {
183        let mut out = [T::zero(); N];
184        for i in 0..N {
185            out[i] = self.data[N - 1 - i];
186        }
187        Self { data: out }
188    }
189
190    /// True when every element compares equal to the corresponding one in `other`.
191    pub fn all_eq(&self, other: &Self) -> bool
192    where
193        T: PartialEq,
194    {
195        self.data == other.data
196    }
197}
198
199impl<T: Num, const N: usize> Array<T, N> {
200    /// Element-wise addition.
201    pub fn add(&self, other: &Self) -> Self {
202        self.zip_with(other, |x, y| x + y)
203    }
204
205    /// Element-wise subtraction.
206    pub fn sub(&self, other: &Self) -> Self {
207        self.zip_with(other, |x, y| x - y)
208    }
209
210    /// Scalar multiply: `self * s`.
211    pub fn scalar_mul(&self, s: T) -> Self {
212        self.map(|x| x * s)
213    }
214
215    /// Scalar addition: `self + s`.
216    pub fn scalar_add(&self, s: T) -> Self {
217        self.map(|x| x + s)
218    }
219
220    /// Divide every element by `s`.
221    pub fn scalar_div(&self, s: T) -> Self
222    where
223        T: Div<Output = T>,
224    {
225        self.map(|x| x / s)
226    }
227}
228
229impl<T: Real, const N: usize> Array<T, N> {
230    /// Euclidean (L2) norm.
231    ///
232    /// ```
233    /// use mathverse_core::arrays::Array;
234    /// let a = Array::from([3.0f64, 4.0]);
235    /// assert!((a.l2_norm() - 5.0).abs() < 1e-12);
236    /// ```
237    pub fn l2_norm(&self) -> T {
238        self.dot(self).sqrt()
239    }
240
241    /// Manhattan (L1) norm.
242    pub fn l1_norm(&self) -> T {
243        let mut acc = T::zero();
244        for &x in &self.data {
245            acc = acc + x.abs();
246        }
247        acc
248    }
249
250    /// Maximum (L∞ / Chebyshev) norm.
251    pub fn max_norm(&self) -> T {
252        let mut acc = T::zero();
253        for &x in &self.data {
254            let ax = x.abs();
255            if ax > acc {
256                acc = ax;
257            }
258        }
259        acc
260    }
261
262    /// Normalize to unit length; returns `None` for a zero vector.
263    pub fn try_normalize(&self) -> Option<Self> {
264        let n = self.l2_norm();
265        if n == T::zero() {
266            None
267        } else {
268            Some(self.scalar_div(n))
269        }
270    }
271
272    /// Normalize to unit length; a zero vector stays zero.
273    pub fn normalize(&self) -> Self {
274        self.try_normalize().unwrap_or_else(Self::zeros)
275    }
276
277    /// Mean of the elements.
278    pub fn mean(&self) -> T {
279        self.sum() / T::from_f64(N as f64)
280    }
281}
282
283impl<T, const N: usize> From<[T; N]> for Array<T, N> {
284    fn from(data: [T; N]) -> Self {
285        Self { data }
286    }
287}
288
289impl<T, const N: usize> From<Array<T, N>> for [T; N] {
290    fn from(arr: Array<T, N>) -> Self {
291        arr.data
292    }
293}
294
295impl<T, const N: usize> AsRef<[T]> for Array<T, N> {
296    fn as_ref(&self) -> &[T] {
297        &self.data
298    }
299}
300
301impl<T, const N: usize> core::ops::Index<usize> for Array<T, N> {
302    type Output = T;
303
304    fn index(&self, index: usize) -> &Self::Output {
305        &self.data[index]
306    }
307}
308
309impl<T: Num, const N: usize> Add for Array<T, N> {
310    type Output = Self;
311
312    fn add(self, rhs: Self) -> Self::Output {
313        Self::add(&self, &rhs)
314    }
315}
316
317impl<T: Num, const N: usize> Sub for Array<T, N> {
318    type Output = Self;
319
320    fn sub(self, rhs: Self) -> Self::Output {
321        Self::sub(&self, &rhs)
322    }
323}
324
325impl<T: Num, const N: usize> Mul<T> for Array<T, N> {
326    type Output = Self;
327
328    fn mul(self, rhs: T) -> Self::Output {
329        self.scalar_mul(rhs)
330    }
331}
332
333impl<T: Num + Div<Output = T>, const N: usize> Div<T> for Array<T, N> {
334    type Output = Self;
335
336    fn div(self, rhs: T) -> Self::Output {
337        self.scalar_div(rhs)
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn basic_ops() {
347        let a = Array::from([1.0f64, 2.0, 3.0]);
348        let b = Array::from([4.0, 5.0, 6.0]);
349        assert_eq!(a + b, Array::from([5.0, 7.0, 9.0]));
350        assert_eq!(a.dot(&b), 32.0);
351        assert_eq!(a.sum(), 6.0);
352    }
353
354    #[test]
355    fn const_generic_sizes() {
356        let a: Array<u32, 4> = Array::ones();
357        assert_eq!(a.sum(), 4);
358        let b: Array<u32, 8> = Array::zeros();
359        assert_eq!(b.sum(), 0);
360    }
361
362    #[test]
363    fn norms() {
364        let a = Array::from([3.0f64, 4.0]);
365        assert!((a.l2_norm() - 5.0).abs() < 1e-12);
366        assert_eq!(a.l1_norm(), 7.0);
367        assert_eq!(a.max_norm(), 4.0);
368        assert!((a.try_normalize().unwrap().l2_norm() - 1.0).abs() < 1e-12);
369    }
370
371    #[test]
372    fn int_arrays() {
373        let a = Array::from([1i32, 2, 3]);
374        let b = Array::from([10, 20, 30]);
375        assert_eq!(a.component_mul(&b), Array::from([10, 40, 90]));
376        assert_eq!(a.cumulative_sum(), Array::from([1, 3, 6]));
377    }
378
379    #[test]
380    fn normalize_zero() {
381        let z: Array<f64, 3> = Array::zeros();
382        assert!(z.try_normalize().is_none());
383        assert_eq!(z.normalize(), z);
384    }
385}