Skip to main content

loeres_backend_std/
dense.rs

1//! Heap-backed dense vectors and matrices (RFC 007).
2//!
3//! `DenseVector<S>` wraps a `Vec<S>`; `DenseMatrix<S>` is a row-major `Vec<S>`
4//! with `rows`/`cols`. Both implement the RFC 002 access contracts and report
5//! `DimensionKind::Dynamic`. Server-only (`loeres-backend-std`).
6
7use loeres::{
8    ContiguousMatrixAccess, ContiguousVectorAccess, ContiguousVectorAccessMut, Dim2, DimensionKind,
9    FiniteScalar, MatrixAccess, MatrixAccessMut, SolverError, VectorAccess, VectorAccessMut,
10};
11
12use crate::internal::dimension_mismatch;
13
14/// Pre-allocation memory limit for dense ingestion (RFC 007 §3.5).
15///
16/// `max_elements` bounds the **final element count** of the constructed adapter
17/// (`len` for a vector, `rows * cols` for a matrix), not the raw payload length.
18#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
19pub struct DenseIngestOptions {
20    /// Maximum final element count accepted, or `None` for no limit.
21    pub max_elements: Option<usize>,
22}
23
24/// A dynamically sized, heap-backed dense vector.
25#[derive(Clone, Debug)]
26pub struct DenseVector<S> {
27    data: Vec<S>,
28}
29
30impl<S: loeres::BaseScalar> DenseVector<S> {
31    /// Build from an owned `Vec<S>` (no memory limit).
32    pub fn from_vec(data: Vec<S>) -> Result<Self, SolverError> {
33        Self::from_vec_with_options(data, DenseIngestOptions::default())
34    }
35
36    /// Build from an owned `Vec<S>`, rejecting payloads over `max_elements`
37    /// with `SolverError::InvalidInput`. An empty vector is rejected with
38    /// `SolverError::InvalidDimension` (zero length), checked before the limit.
39    pub fn from_vec_with_options(
40        data: Vec<S>,
41        options: DenseIngestOptions,
42    ) -> Result<Self, SolverError> {
43        if data.is_empty() {
44            return Err(SolverError::InvalidDimension);
45        }
46        if let Some(max) = options.max_elements {
47            if data.len() > max {
48                return Err(SolverError::InvalidInput);
49            }
50        }
51        Ok(Self { data })
52    }
53
54    /// The element count.
55    pub fn len(&self) -> usize {
56        self.data.len()
57    }
58
59    /// Whether the vector is empty.
60    pub fn is_empty(&self) -> bool {
61        self.data.is_empty()
62    }
63}
64
65impl<S: FiniteScalar> DenseVector<S> {
66    /// Scan for non-finite elements, returning `NonFiniteInput` on the first
67    /// one. Validation *state* is RFC 012-owned; this is a plain check helper.
68    pub fn validate_finite(&self) -> Result<(), SolverError> {
69        for value in &self.data {
70            if !value.is_finite() {
71                return Err(SolverError::NonFiniteInput);
72            }
73        }
74        Ok(())
75    }
76}
77
78impl<S: loeres::BaseScalar> VectorAccess for DenseVector<S> {
79    type Scalar = S;
80
81    fn len(&self) -> usize {
82        self.data.len()
83    }
84
85    fn dimension_kind(&self) -> DimensionKind {
86        DimensionKind::Dynamic
87    }
88
89    fn get(&self, index: usize) -> Result<S, SolverError> {
90        match self.data.get(index) {
91            Some(&value) => Ok(value),
92            None => Err(dimension_mismatch(index, self.data.len())),
93        }
94    }
95}
96
97impl<S: loeres::BaseScalar> VectorAccessMut for DenseVector<S> {
98    fn set(&mut self, index: usize, value: S) -> Result<(), SolverError> {
99        let len = self.data.len();
100        match self.data.get_mut(index) {
101            Some(slot) => {
102                *slot = value;
103                Ok(())
104            }
105            None => Err(dimension_mismatch(index, len)),
106        }
107    }
108}
109
110impl<S: loeres::BaseScalar> ContiguousVectorAccess for DenseVector<S> {
111    fn as_contiguous(&self) -> Option<&[S]> {
112        Some(&self.data)
113    }
114}
115
116impl<S: loeres::BaseScalar> ContiguousVectorAccessMut for DenseVector<S> {
117    fn as_contiguous_mut(&mut self) -> Option<&mut [S]> {
118        Some(&mut self.data)
119    }
120}
121
122/// A dynamically sized, heap-backed row-major dense matrix.
123#[derive(Clone, Debug)]
124pub struct DenseMatrix<S> {
125    rows: usize,
126    cols: usize,
127    data: Vec<S>,
128}
129
130impl<S: loeres::BaseScalar> DenseMatrix<S> {
131    /// Build a `rows`×`cols` matrix from row-major data (no memory limit).
132    pub fn from_row_major_vec(rows: usize, cols: usize, data: Vec<S>) -> Result<Self, SolverError> {
133        Self::from_row_major_vec_with_options(rows, cols, data, DenseIngestOptions::default())
134    }
135
136    /// Build a `rows`×`cols` matrix from row-major data, rejecting an element
137    /// count over `max_elements` with `SolverError::InvalidInput`.
138    pub fn from_row_major_vec_with_options(
139        rows: usize,
140        cols: usize,
141        data: Vec<S>,
142        options: DenseIngestOptions,
143    ) -> Result<Self, SolverError> {
144        if rows == 0 || cols == 0 {
145            return Err(SolverError::InvalidDimension);
146        }
147        let required = rows
148            .checked_mul(cols)
149            .ok_or(SolverError::InvalidDimension)?;
150        if let Some(max) = options.max_elements {
151            if required > max {
152                return Err(SolverError::InvalidInput);
153            }
154        }
155        if data.len() != required {
156            return Err(dimension_mismatch(data.len(), required));
157        }
158        Ok(Self { rows, cols, data })
159    }
160
161    /// The matrix dimensions.
162    pub fn dims(&self) -> Dim2 {
163        Dim2::new(self.rows, self.cols)
164    }
165}
166
167impl<S: FiniteScalar> DenseMatrix<S> {
168    /// Scan for non-finite elements, returning `NonFiniteInput` on the first.
169    pub fn validate_finite(&self) -> Result<(), SolverError> {
170        for value in &self.data {
171            if !value.is_finite() {
172                return Err(SolverError::NonFiniteInput);
173            }
174        }
175        Ok(())
176    }
177}
178
179impl<S: loeres::BaseScalar> MatrixAccess for DenseMatrix<S> {
180    type Scalar = S;
181
182    fn dims(&self) -> Dim2 {
183        Dim2::new(self.rows, self.cols)
184    }
185
186    fn dimension_kind(&self) -> DimensionKind {
187        DimensionKind::Dynamic
188    }
189
190    fn get(&self, row: usize, col: usize) -> Result<S, SolverError> {
191        if row >= self.rows {
192            return Err(dimension_mismatch(row, self.rows));
193        }
194        if col >= self.cols {
195            return Err(dimension_mismatch(col, self.cols));
196        }
197        // row < rows and col < cols, so row * cols + col is in bounds.
198        let offset = row * self.cols + col;
199        match self.data.get(offset) {
200            Some(&value) => Ok(value),
201            None => Err(SolverError::InternalInvariantViolation),
202        }
203    }
204}
205
206impl<S: loeres::BaseScalar> MatrixAccessMut for DenseMatrix<S> {
207    fn set(&mut self, row: usize, col: usize, value: S) -> Result<(), SolverError> {
208        if row >= self.rows {
209            return Err(dimension_mismatch(row, self.rows));
210        }
211        if col >= self.cols {
212            return Err(dimension_mismatch(col, self.cols));
213        }
214        let offset = row * self.cols + col;
215        match self.data.get_mut(offset) {
216            Some(slot) => {
217                *slot = value;
218                Ok(())
219            }
220            None => Err(SolverError::InternalInvariantViolation),
221        }
222    }
223}
224
225impl<S: loeres::BaseScalar> ContiguousMatrixAccess for DenseMatrix<S> {
226    fn as_row_major(&self) -> Option<&[S]> {
227        Some(&self.data)
228    }
229}
230
231#[cfg(test)]
232mod tests;