1use std::ops::{Index, IndexMut};
8
9use thiserror::Error;
10
11#[derive(Debug, Error, Clone, PartialEq, Eq)]
13pub enum MatrixError {
14 #[error("matrix dimensions {rows}x{cols} overflow the address space")]
16 DimensionsOverflow {
17 rows: usize,
19 cols: usize,
21 },
22 #[error("matrix shape {rows}x{cols} requires {expected} values, got {actual}")]
24 InvalidShape {
25 rows: usize,
27 cols: usize,
29 expected: usize,
31 actual: usize,
33 },
34 #[error("matrix row {row} has {actual} columns; expected {expected}")]
36 RaggedRows {
37 row: usize,
39 expected: usize,
41 actual: usize,
43 },
44}
45
46#[derive(Clone, Debug, PartialEq)]
48#[cfg_attr(
49 feature = "serde",
50 derive(serde::Serialize, serde::Deserialize),
51 serde(try_from = "MatrixData", into = "MatrixData")
52)]
53pub struct Matrix {
54 rows: usize,
55 cols: usize,
56 data: Vec<f64>,
57}
58
59#[cfg(feature = "serde")]
62#[derive(serde::Serialize, serde::Deserialize)]
63struct MatrixData {
64 rows: usize,
65 cols: usize,
66 data: Vec<f64>,
67}
68
69#[cfg(feature = "serde")]
70impl TryFrom<MatrixData> for Matrix {
71 type Error = MatrixError;
72
73 fn try_from(data: MatrixData) -> Result<Self, Self::Error> {
74 Self::new(data.rows, data.cols, data.data)
75 }
76}
77
78#[cfg(feature = "serde")]
79impl From<Matrix> for MatrixData {
80 fn from(matrix: Matrix) -> Self {
81 Self {
82 rows: matrix.rows,
83 cols: matrix.cols,
84 data: matrix.data,
85 }
86 }
87}
88
89impl Matrix {
90 pub fn new(rows: usize, cols: usize, data: Vec<f64>) -> Result<Self, MatrixError> {
97 let expected = rows
98 .checked_mul(cols)
99 .ok_or(MatrixError::DimensionsOverflow { rows, cols })?;
100 if data.len() != expected {
101 return Err(MatrixError::InvalidShape {
102 rows,
103 cols,
104 expected,
105 actual: data.len(),
106 });
107 }
108 Ok(Self { rows, cols, data })
109 }
110
111 pub fn from_rows(rows: Vec<Vec<f64>>) -> Result<Self, MatrixError> {
117 let row_count = rows.len();
118 let cols = rows.first().map_or(0, Vec::len);
119 let mut data = Vec::with_capacity(row_count.saturating_mul(cols));
120 for (row_index, row) in rows.into_iter().enumerate() {
121 if row.len() != cols {
122 return Err(MatrixError::RaggedRows {
123 row: row_index,
124 expected: cols,
125 actual: row.len(),
126 });
127 }
128 data.extend(row);
129 }
130 Ok(Self {
131 rows: row_count,
132 cols,
133 data,
134 })
135 }
136
137 #[must_use]
139 pub fn zeros(rows: usize, cols: usize) -> Self {
140 Self {
141 rows,
142 cols,
143 data: vec![0.0; rows.saturating_mul(cols)],
144 }
145 }
146
147 #[must_use]
149 pub const fn rows(&self) -> usize {
150 self.rows
151 }
152
153 #[must_use]
155 pub const fn cols(&self) -> usize {
156 self.cols
157 }
158
159 #[must_use]
161 pub fn as_slice(&self) -> &[f64] {
162 &self.data
163 }
164
165 #[must_use]
167 pub fn as_mut_slice(&mut self) -> &mut [f64] {
168 &mut self.data
169 }
170
171 #[must_use]
173 pub fn row(&self, row: usize) -> &[f64] {
174 let start = row * self.cols;
175 &self.data[start..start + self.cols]
176 }
177
178 pub(crate) fn mul_vec(&self, x: &[f64]) -> Vec<f64> {
179 debug_assert_eq!(self.cols, x.len());
180 (0..self.rows).map(|row| dot(self.row(row), x)).collect()
181 }
182
183 pub(crate) fn transpose_mul_add(&self, x: &[f64], output: &mut [f64]) {
184 debug_assert_eq!(self.rows, x.len());
185 debug_assert_eq!(self.cols, output.len());
186 for (row, &scale) in x.iter().enumerate() {
187 for col in 0..self.cols {
188 output[col] += self[(row, col)] * scale;
189 }
190 }
191 }
192}
193
194impl Index<(usize, usize)> for Matrix {
195 type Output = f64;
196
197 fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
198 &self.data[row * self.cols + col]
199 }
200}
201
202impl IndexMut<(usize, usize)> for Matrix {
203 fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
204 &mut self.data[row * self.cols + col]
205 }
206}
207
208pub(crate) fn dot(left: &[f64], right: &[f64]) -> f64 {
209 debug_assert_eq!(left.len(), right.len());
210 left.iter().zip(right).map(|(a, b)| a * b).sum()
211}
212
213pub(crate) fn norm_inf(values: &[f64]) -> f64 {
214 values.iter().fold(0.0, |largest, value| {
215 if value.is_nan() {
216 f64::NAN
217 } else {
218 largest.max(value.abs())
219 }
220 })
221}