Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}