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