Skip to main content

antecedent_kernels/
view.rs

1//! Library-owned borrowed vector and matrix views.
2//!
3//! Public APIs expose these views; SIMD types are never part of the public API.
4//!
5//! SPDX-License-Identifier: MIT OR Apache-2.0
6
7use core::fmt;
8
9/// Errors when constructing or indexing views.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum ViewError {
12    /// Shape or stride inconsistency.
13    InvalidShape {
14        /// Explanation.
15        message: &'static str,
16    },
17    /// Index out of bounds.
18    OutOfBounds {
19        /// Requested index.
20        index: usize,
21        /// Valid length.
22        len: usize,
23    },
24}
25
26impl fmt::Display for ViewError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::InvalidShape { message } => write!(f, "invalid view shape: {message}"),
30            Self::OutOfBounds { index, len } => {
31                write!(f, "index {index} out of bounds for length {len}")
32            }
33        }
34    }
35}
36
37impl std::error::Error for ViewError {}
38
39/// Borrowed strided `f64` vector view.
40#[derive(Clone, Copy, Debug)]
41pub struct F64VectorView<'a> {
42    data: &'a [f64],
43    len: usize,
44    stride: usize,
45}
46
47impl<'a> F64VectorView<'a> {
48    /// Contiguous vector view over `data`.
49    #[must_use]
50    pub const fn contiguous(data: &'a [f64]) -> Self {
51        Self { data, len: data.len(), stride: 1 }
52    }
53
54    /// Strided view. The underlying slice must contain at least
55    /// `(len.saturating_sub(1)) * stride + 1` elements when `len > 0`.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`ViewError::InvalidShape`] when the slice is too short.
60    pub fn strided(data: &'a [f64], len: usize, stride: usize) -> Result<Self, ViewError> {
61        if len == 0 {
62            return Ok(Self { data, len: 0, stride: stride.max(1) });
63        }
64        if stride == 0 {
65            return Err(ViewError::InvalidShape { message: "stride must be non-zero" });
66        }
67        let need = (len - 1)
68            .checked_mul(stride)
69            .and_then(|v| v.checked_add(1))
70            .ok_or(ViewError::InvalidShape { message: "stride*len overflow" })?;
71        if data.len() < need {
72            return Err(ViewError::InvalidShape {
73                message: "underlying slice shorter than strided extent",
74            });
75        }
76        Ok(Self { data, len, stride })
77    }
78
79    /// Number of logical elements.
80    #[must_use]
81    pub const fn len(self) -> usize {
82        self.len
83    }
84
85    /// Whether empty.
86    #[must_use]
87    pub const fn is_empty(self) -> bool {
88        self.len == 0
89    }
90
91    /// Stride between logical elements.
92    #[must_use]
93    pub const fn stride(self) -> usize {
94        self.stride
95    }
96
97    /// Whether the view is unit-stride contiguous over `len` elements.
98    #[must_use]
99    pub const fn is_contiguous(self) -> bool {
100        self.stride == 1
101    }
102
103    /// Element at logical index `i`.
104    ///
105    /// # Errors
106    ///
107    /// Returns [`ViewError::OutOfBounds`] when `i >= len`.
108    pub fn get(self, i: usize) -> Result<f64, ViewError> {
109        if i >= self.len {
110            return Err(ViewError::OutOfBounds { index: i, len: self.len });
111        }
112        Ok(self.data[i * self.stride])
113    }
114
115    /// Unchecked element access for hot loops after bounds are established.
116    ///
117    /// # Safety
118    ///
119    /// Caller must ensure `i < self.len`.
120    #[inline]
121    #[must_use]
122    pub unsafe fn get_unchecked(self, i: usize) -> f64 {
123        // SAFETY: caller guarantees i < len; stride construction validated extent.
124        unsafe { *self.data.get_unchecked(i * self.stride) }
125    }
126
127    /// Contiguous slice when unit-stride; otherwise `None`.
128    #[must_use]
129    pub fn as_slice(self) -> Option<&'a [f64]> {
130        if self.is_contiguous() { Some(&self.data[..self.len]) } else { None }
131    }
132}
133
134/// Borrowed column-major or row-major `f64` matrix view.
135#[derive(Clone, Copy, Debug)]
136pub struct F64MatrixView<'a> {
137    data: &'a [f64],
138    nrows: usize,
139    ncols: usize,
140    row_stride: usize,
141    col_stride: usize,
142}
143
144impl<'a> F64MatrixView<'a> {
145    /// Column-major contiguous matrix (`faer`-friendly default).
146    ///
147    /// # Errors
148    ///
149    /// Returns [`ViewError::InvalidShape`] when `data.len() < nrows * ncols`.
150    pub fn column_major(data: &'a [f64], nrows: usize, ncols: usize) -> Result<Self, ViewError> {
151        let need = nrows
152            .checked_mul(ncols)
153            .ok_or(ViewError::InvalidShape { message: "nrows*ncols overflow" })?;
154        if data.len() < need {
155            return Err(ViewError::InvalidShape { message: "buffer shorter than matrix" });
156        }
157        Ok(Self { data, nrows, ncols, row_stride: 1, col_stride: nrows })
158    }
159
160    /// Number of rows.
161    #[must_use]
162    pub const fn nrows(self) -> usize {
163        self.nrows
164    }
165
166    /// Number of columns.
167    #[must_use]
168    pub const fn ncols(self) -> usize {
169        self.ncols
170    }
171
172    /// Element at `(row, col)`.
173    ///
174    /// # Errors
175    ///
176    /// Out-of-bounds indices.
177    pub fn get(self, row: usize, col: usize) -> Result<f64, ViewError> {
178        if row >= self.nrows {
179            return Err(ViewError::OutOfBounds { index: row, len: self.nrows });
180        }
181        if col >= self.ncols {
182            return Err(ViewError::OutOfBounds { index: col, len: self.ncols });
183        }
184        Ok(self.data[row * self.row_stride + col * self.col_stride])
185    }
186
187    /// Column `j` as a vector view.
188    ///
189    /// # Errors
190    ///
191    /// Out-of-bounds column.
192    pub fn column(self, j: usize) -> Result<F64VectorView<'a>, ViewError> {
193        if j >= self.ncols {
194            return Err(ViewError::OutOfBounds { index: j, len: self.ncols });
195        }
196        let offset = j * self.col_stride;
197        F64VectorView::strided(&self.data[offset..], self.nrows, self.row_stride)
198    }
199}
200
201/// Optional validity / analysis mask as a packed bitmap (`1` = valid/included).
202#[derive(Clone, Copy, Debug)]
203pub struct BitMaskView<'a> {
204    bytes: &'a [u8],
205    len: usize,
206}
207
208impl<'a> BitMaskView<'a> {
209    /// Create a mask covering `len` bits.
210    ///
211    /// # Errors
212    ///
213    /// When `bytes` is shorter than `ceil(len / 8)`.
214    pub fn new(bytes: &'a [u8], len: usize) -> Result<Self, ViewError> {
215        let need = len.div_ceil(8);
216        if bytes.len() < need {
217            return Err(ViewError::InvalidShape { message: "mask buffer too short" });
218        }
219        Ok(Self { bytes, len })
220    }
221
222    /// Number of bits.
223    #[must_use]
224    pub const fn len(self) -> usize {
225        self.len
226    }
227
228    /// Whether empty.
229    #[must_use]
230    pub const fn is_empty(self) -> bool {
231        self.len == 0
232    }
233
234    /// Whether bit `i` is set.
235    #[must_use]
236    pub fn get(self, i: usize) -> bool {
237        if i >= self.len {
238            return false;
239        }
240        let byte = self.bytes[i / 8];
241        (byte >> (i % 8)) & 1 == 1
242    }
243}