/home/noah/src/trueno/src/matrix/ops/storage.rs
Line | Count | Source |
1 | | //! Matrix storage and construction operations |
2 | | //! |
3 | | //! This module provides storage-related operations for Matrix: |
4 | | //! - Constructors: `new()`, `from_vec()`, `from_slice()`, `zeros()`, `identity()` |
5 | | //! - Accessors: `rows()`, `cols()`, `shape()`, `get()`, `get_mut()`, `as_slice()` |
6 | | //! |
7 | | //! ## Domain Separation (PMAT-018) |
8 | | //! |
9 | | //! Storage is separate from Algebra (arithmetic operations). |
10 | | //! A matrix's memory layout is independent of its mathematical operations. |
11 | | |
12 | | use crate::{Backend, TruenoError}; |
13 | | |
14 | | use super::super::Matrix; |
15 | | |
16 | | impl std::ops::Index<(usize, usize)> for Matrix<f32> { |
17 | | type Output = f32; |
18 | | |
19 | 0 | fn index(&self, (row, col): (usize, usize)) -> &Self::Output { |
20 | 0 | &self.data[row * self.cols + col] |
21 | 0 | } |
22 | | } |
23 | | |
24 | | impl Matrix<f32> { |
25 | | // ========================================================================= |
26 | | // Constructors |
27 | | // ========================================================================= |
28 | | |
29 | | /// Creates a new matrix with uninitialized values |
30 | | /// |
31 | | /// # Arguments |
32 | | /// |
33 | | /// * `rows` - Number of rows |
34 | | /// * `cols` - Number of columns |
35 | | /// |
36 | | /// # Returns |
37 | | /// |
38 | | /// A new matrix with dimensions `rows x cols` containing uninitialized values |
39 | | /// |
40 | | /// # Example |
41 | | /// |
42 | | /// ``` |
43 | | /// use trueno::Matrix; |
44 | | /// |
45 | | /// let m = Matrix::new(3, 4); |
46 | | /// assert_eq!(m.rows(), 3); |
47 | | /// assert_eq!(m.cols(), 4); |
48 | | /// ``` |
49 | 0 | pub fn new(rows: usize, cols: usize) -> Self { |
50 | 0 | let backend = Backend::select_best(); |
51 | 0 | Matrix { |
52 | 0 | rows, |
53 | 0 | cols, |
54 | 0 | data: vec![0.0; rows * cols], |
55 | 0 | backend, |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | /// Creates a matrix from a vector of data |
60 | | /// |
61 | | /// # Arguments |
62 | | /// |
63 | | /// * `rows` - Number of rows |
64 | | /// * `cols` - Number of columns |
65 | | /// * `data` - Vector containing matrix elements in row-major order |
66 | | /// |
67 | | /// # Errors |
68 | | /// |
69 | | /// Returns `InvalidInput` if `data.len() != rows * cols` |
70 | | /// |
71 | | /// # Example |
72 | | /// |
73 | | /// ``` |
74 | | /// use trueno::Matrix; |
75 | | /// |
76 | | /// let m = Matrix::from_vec(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); |
77 | | /// assert_eq!(m.rows(), 2); |
78 | | /// assert_eq!(m.cols(), 2); |
79 | | /// ``` |
80 | 10 | pub fn from_vec(rows: usize, cols: usize, data: Vec<f32>) -> Result<Self, TruenoError> { |
81 | 10 | if data.len() != rows * cols { |
82 | 0 | return Err(TruenoError::InvalidInput(format!( |
83 | 0 | "Data length {} does not match matrix dimensions {}x{} (expected {})", |
84 | 0 | data.len(), |
85 | 0 | rows, |
86 | 0 | cols, |
87 | 0 | rows * cols |
88 | 0 | ))); |
89 | 10 | } |
90 | | |
91 | 10 | let backend = Backend::select_best(); |
92 | 10 | Ok(Matrix { |
93 | 10 | rows, |
94 | 10 | cols, |
95 | 10 | data, |
96 | 10 | backend, |
97 | 10 | }) |
98 | 10 | } |
99 | | |
100 | | /// Creates a matrix from a vector with a specific backend |
101 | | /// |
102 | | /// This is useful for testing specific SIMD code paths. |
103 | 0 | pub fn from_vec_with_backend( |
104 | 0 | rows: usize, |
105 | 0 | cols: usize, |
106 | 0 | data: Vec<f32>, |
107 | 0 | backend: Backend, |
108 | 0 | ) -> Self { |
109 | 0 | assert_eq!( |
110 | 0 | data.len(), |
111 | 0 | rows * cols, |
112 | 0 | "Data length {} does not match matrix dimensions {}x{}", |
113 | 0 | data.len(), |
114 | | rows, |
115 | | cols |
116 | | ); |
117 | 0 | Matrix { |
118 | 0 | rows, |
119 | 0 | cols, |
120 | 0 | data, |
121 | 0 | backend, |
122 | 0 | } |
123 | 0 | } |
124 | | |
125 | | /// Creates a matrix from a slice by copying the data |
126 | | /// |
127 | | /// This is a convenience method that copies the slice into an owned vector. |
128 | | /// For zero-copy scenarios, consider using the data directly with `from_vec` |
129 | | /// if you already have an owned `Vec`. |
130 | | /// |
131 | | /// # Arguments |
132 | | /// |
133 | | /// * `rows` - Number of rows |
134 | | /// * `cols` - Number of columns |
135 | | /// * `data` - Slice containing matrix elements in row-major order |
136 | | /// |
137 | | /// # Errors |
138 | | /// |
139 | | /// Returns `InvalidInput` if `data.len() != rows * cols` |
140 | | /// |
141 | | /// # Example |
142 | | /// |
143 | | /// ``` |
144 | | /// use trueno::Matrix; |
145 | | /// |
146 | | /// let data = [1.0, 2.0, 3.0, 4.0]; |
147 | | /// let m = Matrix::from_slice(2, 2, &data).unwrap(); |
148 | | /// assert_eq!(m.get(0, 0), Some(&1.0)); |
149 | | /// ``` |
150 | 0 | pub fn from_slice(rows: usize, cols: usize, data: &[f32]) -> Result<Self, TruenoError> { |
151 | 0 | Self::from_vec(rows, cols, data.to_vec()) |
152 | 0 | } |
153 | | |
154 | | /// Creates a matrix filled with zeros |
155 | | /// |
156 | | /// # Example |
157 | | /// |
158 | | /// ``` |
159 | | /// use trueno::Matrix; |
160 | | /// |
161 | | /// let m = Matrix::zeros(3, 3); |
162 | | /// assert_eq!(m.get(1, 1), Some(&0.0)); |
163 | | /// ``` |
164 | 0 | pub fn zeros(rows: usize, cols: usize) -> Self { |
165 | 0 | Matrix::new(rows, cols) |
166 | 0 | } |
167 | | |
168 | | /// Creates a matrix filled with zeros using a specific backend |
169 | | /// (Internal use only - reuses backend from parent matrix) |
170 | 0 | pub(crate) fn zeros_with_backend(rows: usize, cols: usize, backend: Backend) -> Self { |
171 | 0 | Matrix { |
172 | 0 | rows, |
173 | 0 | cols, |
174 | 0 | data: vec![0.0; rows * cols], |
175 | 0 | backend, |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | /// Creates an identity matrix (square matrix with 1s on diagonal) |
180 | | /// |
181 | | /// # Example |
182 | | /// |
183 | | /// ``` |
184 | | /// use trueno::Matrix; |
185 | | /// |
186 | | /// let m = Matrix::identity(3); |
187 | | /// assert_eq!(m.get(0, 0), Some(&1.0)); |
188 | | /// assert_eq!(m.get(0, 1), Some(&0.0)); |
189 | | /// assert_eq!(m.get(1, 1), Some(&1.0)); |
190 | | /// ``` |
191 | 0 | pub fn identity(n: usize) -> Self { |
192 | 0 | let mut data = vec![0.0; n * n]; |
193 | 0 | for i in 0..n { |
194 | 0 | data[i * n + i] = 1.0; |
195 | 0 | } |
196 | 0 | let backend = Backend::select_best(); |
197 | 0 | Matrix { |
198 | 0 | rows: n, |
199 | 0 | cols: n, |
200 | 0 | data, |
201 | 0 | backend, |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | // ========================================================================= |
206 | | // Accessors |
207 | | // ========================================================================= |
208 | | |
209 | | /// Returns the number of rows |
210 | 0 | pub fn rows(&self) -> usize { |
211 | 0 | self.rows |
212 | 0 | } |
213 | | |
214 | | /// Returns the number of columns |
215 | 0 | pub fn cols(&self) -> usize { |
216 | 0 | self.cols |
217 | 0 | } |
218 | | |
219 | | /// Returns the shape as (rows, cols) |
220 | 0 | pub fn shape(&self) -> (usize, usize) { |
221 | 0 | (self.rows, self.cols) |
222 | 0 | } |
223 | | |
224 | | /// Gets a reference to an element at (row, col) |
225 | | /// |
226 | | /// Returns `None` if indices are out of bounds |
227 | 0 | pub fn get(&self, row: usize, col: usize) -> Option<&f32> { |
228 | 0 | if row >= self.rows || col >= self.cols { |
229 | 0 | None |
230 | | } else { |
231 | 0 | self.data.get(row * self.cols + col) |
232 | | } |
233 | 0 | } |
234 | | |
235 | | /// Gets a mutable reference to an element at (row, col) |
236 | | /// |
237 | | /// Returns `None` if indices are out of bounds |
238 | 0 | pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut f32> { |
239 | 0 | if row >= self.rows || col >= self.cols { |
240 | 0 | None |
241 | | } else { |
242 | 0 | let idx = row * self.cols + col; |
243 | 0 | self.data.get_mut(idx) |
244 | | } |
245 | 0 | } |
246 | | |
247 | | /// Returns a reference to the underlying data |
248 | 0 | pub fn as_slice(&self) -> &[f32] { |
249 | 0 | &self.data |
250 | 0 | } |
251 | | |
252 | | /// Returns the backend used by this matrix |
253 | 0 | pub fn backend(&self) -> Backend { |
254 | 0 | self.backend |
255 | 0 | } |
256 | | } |
257 | | |
258 | | #[cfg(test)] |
259 | | mod tests { |
260 | | use super::*; |
261 | | |
262 | | #[test] |
263 | | fn test_new_creates_zero_matrix() { |
264 | | let m = Matrix::new(3, 4); |
265 | | assert_eq!(m.rows(), 3); |
266 | | assert_eq!(m.cols(), 4); |
267 | | assert!(m.as_slice().iter().all(|&x| x == 0.0)); |
268 | | } |
269 | | |
270 | | #[test] |
271 | | fn test_from_vec_success() { |
272 | | let m = Matrix::from_vec(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); |
273 | | assert_eq!(m.get(0, 0), Some(&1.0)); |
274 | | assert_eq!(m.get(1, 1), Some(&4.0)); |
275 | | } |
276 | | |
277 | | #[test] |
278 | | fn test_from_vec_dimension_mismatch() { |
279 | | let result = Matrix::from_vec(2, 2, vec![1.0, 2.0, 3.0]); |
280 | | assert!(result.is_err()); |
281 | | } |
282 | | |
283 | | #[test] |
284 | | fn test_identity() { |
285 | | let m = Matrix::identity(3); |
286 | | assert_eq!(m.get(0, 0), Some(&1.0)); |
287 | | assert_eq!(m.get(1, 1), Some(&1.0)); |
288 | | assert_eq!(m.get(2, 2), Some(&1.0)); |
289 | | assert_eq!(m.get(0, 1), Some(&0.0)); |
290 | | } |
291 | | |
292 | | #[test] |
293 | | fn test_index_operator() { |
294 | | let m = Matrix::from_vec(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); |
295 | | assert_eq!(m[(0, 0)], 1.0); |
296 | | assert_eq!(m[(1, 1)], 4.0); |
297 | | } |
298 | | |
299 | | #[test] |
300 | | fn test_get_out_of_bounds() { |
301 | | let m = Matrix::new(2, 2); |
302 | | assert_eq!(m.get(2, 0), None); |
303 | | assert_eq!(m.get(0, 2), None); |
304 | | } |
305 | | |
306 | | #[test] |
307 | | fn test_get_mut() { |
308 | | let mut m = Matrix::new(2, 2); |
309 | | if let Some(val) = m.get_mut(1, 1) { |
310 | | *val = 42.0; |
311 | | } |
312 | | assert_eq!(m.get(1, 1), Some(&42.0)); |
313 | | } |
314 | | } |