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