use std::ops::{Index, IndexMut};
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum MatrixError {
#[error("matrix dimensions {rows}x{cols} overflow the address space")]
DimensionsOverflow {
rows: usize,
cols: usize,
},
#[error("matrix shape {rows}x{cols} requires {expected} values, got {actual}")]
InvalidShape {
rows: usize,
cols: usize,
expected: usize,
actual: usize,
},
#[error("matrix row {row} has {actual} columns; expected {expected}")]
RaggedRows {
row: usize,
expected: usize,
actual: usize,
},
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "MatrixData", into = "MatrixData")
)]
pub struct Matrix {
rows: usize,
cols: usize,
data: Vec<f64>,
}
#[cfg(feature = "serde")]
#[derive(serde::Serialize, serde::Deserialize)]
struct MatrixData {
rows: usize,
cols: usize,
data: Vec<f64>,
}
#[cfg(feature = "serde")]
impl TryFrom<MatrixData> for Matrix {
type Error = MatrixError;
fn try_from(data: MatrixData) -> Result<Self, Self::Error> {
Self::new(data.rows, data.cols, data.data)
}
}
#[cfg(feature = "serde")]
impl From<Matrix> for MatrixData {
fn from(matrix: Matrix) -> Self {
Self {
rows: matrix.rows,
cols: matrix.cols,
data: matrix.data,
}
}
}
impl Matrix {
pub fn new(rows: usize, cols: usize, data: Vec<f64>) -> Result<Self, MatrixError> {
let expected = rows
.checked_mul(cols)
.ok_or(MatrixError::DimensionsOverflow { rows, cols })?;
if data.len() != expected {
return Err(MatrixError::InvalidShape {
rows,
cols,
expected,
actual: data.len(),
});
}
Ok(Self { rows, cols, data })
}
pub fn from_rows(rows: Vec<Vec<f64>>) -> Result<Self, MatrixError> {
let row_count = rows.len();
let cols = rows.first().map_or(0, Vec::len);
let mut data = Vec::with_capacity(row_count.saturating_mul(cols));
for (row_index, row) in rows.into_iter().enumerate() {
if row.len() != cols {
return Err(MatrixError::RaggedRows {
row: row_index,
expected: cols,
actual: row.len(),
});
}
data.extend(row);
}
Ok(Self {
rows: row_count,
cols,
data,
})
}
#[must_use]
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
data: vec![0.0; rows.saturating_mul(cols)],
}
}
#[must_use]
pub const fn rows(&self) -> usize {
self.rows
}
#[must_use]
pub const fn cols(&self) -> usize {
self.cols
}
#[must_use]
pub fn as_slice(&self) -> &[f64] {
&self.data
}
#[must_use]
pub fn as_mut_slice(&mut self) -> &mut [f64] {
&mut self.data
}
#[must_use]
pub fn row(&self, row: usize) -> &[f64] {
let start = row * self.cols;
&self.data[start..start + self.cols]
}
pub(crate) fn mul_vec(&self, x: &[f64]) -> Vec<f64> {
debug_assert_eq!(self.cols, x.len());
(0..self.rows).map(|row| dot(self.row(row), x)).collect()
}
pub(crate) fn transpose_mul_add(&self, x: &[f64], output: &mut [f64]) {
debug_assert_eq!(self.rows, x.len());
debug_assert_eq!(self.cols, output.len());
for (row, &scale) in x.iter().enumerate() {
for col in 0..self.cols {
output[col] += self[(row, col)] * scale;
}
}
}
}
impl Index<(usize, usize)> for Matrix {
type Output = f64;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.data[row * self.cols + col]
}
}
impl IndexMut<(usize, usize)> for Matrix {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
&mut self.data[row * self.cols + col]
}
}
pub(crate) fn dot(left: &[f64], right: &[f64]) -> f64 {
debug_assert_eq!(left.len(), right.len());
left.iter().zip(right).map(|(a, b)| a * b).sum()
}
pub(crate) fn norm_inf(values: &[f64]) -> f64 {
values.iter().fold(0.0, |largest, value| {
if value.is_nan() {
f64::NAN
} else {
largest.max(value.abs())
}
})
}