use crate::error::AlgebraError;
use crate::semiring::Semiring;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AlgebraLimits {
pub max_dim: usize,
}
impl Default for AlgebraLimits {
fn default() -> Self {
AlgebraLimits { max_dim: 1024 }
}
}
impl AlgebraLimits {
pub fn unlimited() -> Self {
AlgebraLimits {
max_dim: usize::MAX,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix<S: Semiring> {
pub rows: usize,
pub cols: usize,
pub data: Vec<S>,
}
impl<S: Semiring> Matrix<S> {
pub fn new(rows: usize, cols: usize) -> Self {
Self::filled(rows, cols, S::zero())
}
pub fn filled(rows: usize, cols: usize, value: S) -> Self {
Matrix {
rows,
cols,
data: vec![value; rows * cols],
}
}
pub fn identity(n: usize) -> Self {
let mut m = Self::new(n, n);
for i in 0..n {
m.data[i * n + i] = S::one();
}
m
}
pub fn from_rows(rows: Vec<Vec<S>>) -> Result<Self, AlgebraError> {
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
let mut data = Vec::with_capacity(nrows * ncols);
for row in rows {
if row.len() != ncols {
return Err(AlgebraError::Ragged);
}
data.extend(row);
}
Ok(Matrix {
rows: nrows,
cols: ncols,
data,
})
}
pub fn is_square(&self) -> bool {
self.rows == self.cols
}
pub fn get(&self, r: usize, c: usize) -> Result<&S, AlgebraError> {
if r >= self.rows || c >= self.cols {
return Err(AlgebraError::IndexOutOfBounds {
index: r.saturating_mul(self.cols).saturating_add(c),
len: self.data.len(),
});
}
Ok(&self.data[r * self.cols + c])
}
pub fn set(&mut self, r: usize, c: usize, value: S) -> Result<(), AlgebraError> {
if r >= self.rows || c >= self.cols {
return Err(AlgebraError::IndexOutOfBounds {
index: r.saturating_mul(self.cols).saturating_add(c),
len: self.data.len(),
});
}
self.data[r * self.cols + c] = value;
Ok(())
}
pub fn row(&self, r: usize) -> Result<&[S], AlgebraError> {
if r >= self.rows {
return Err(AlgebraError::IndexOutOfBounds {
index: r,
len: self.rows,
});
}
Ok(&self.data[r * self.cols..(r + 1) * self.cols])
}
pub fn transpose(&self) -> Self {
let mut data = Vec::with_capacity(self.data.len());
for c in 0..self.cols {
for r in 0..self.rows {
data.push(self.data[r * self.cols + c].clone());
}
}
Matrix {
rows: self.cols,
cols: self.rows,
data,
}
}
pub fn matmul(&self, other: &Self) -> Result<Self, AlgebraError> {
if self.cols != other.rows {
return Err(AlgebraError::ShapeMismatch(format!(
"matmul: {}x{} by {}x{}",
self.rows, self.cols, other.rows, other.cols
)));
}
let mut data = Vec::with_capacity(self.rows * other.cols);
for i in 0..self.rows {
for j in 0..other.cols {
let mut acc = S::zero();
for k in 0..self.cols {
let term = self.data[i * self.cols + k].mul(&other.data[k * other.cols + j]);
acc = acc.add(&term);
}
data.push(acc);
}
}
Ok(Matrix {
rows: self.rows,
cols: other.cols,
data,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Counting;
use crate::tropical_min::MinPlus;
#[test]
fn from_rows_rejects_ragged() {
let r = Matrix::from_rows(vec![
vec![Counting::from_u64(1)],
vec![Counting::from_u64(1), Counting::from_u64(2)],
]);
assert_eq!(r.unwrap_err(), AlgebraError::Ragged);
}
#[test]
fn matmul_known_result_over_counting() {
let a = Matrix::from_rows(vec![
vec![Counting::from_u64(1), Counting::from_u64(2)],
vec![Counting::from_u64(3), Counting::from_u64(4)],
])
.unwrap();
let b = Matrix::from_rows(vec![
vec![Counting::from_u64(5), Counting::from_u64(6)],
vec![Counting::from_u64(7), Counting::from_u64(8)],
])
.unwrap();
let c = a.matmul(&b).unwrap();
assert_eq!(c.data[0], Counting::from_u64(19));
assert_eq!(c.data[1], Counting::from_u64(22));
assert_eq!(c.data[2], Counting::from_u64(43));
assert_eq!(c.data[3], Counting::from_u64(50));
}
#[test]
fn matmul_shape_mismatch() {
let a: Matrix<MinPlus> = Matrix::new(2, 3);
let b: Matrix<MinPlus> = Matrix::new(2, 2);
assert!(matches!(a.matmul(&b), Err(AlgebraError::ShapeMismatch(_))));
}
#[test]
fn identity_is_multiplicative_unit() {
let a = Matrix::from_rows(vec![
vec![Counting::from_u64(1), Counting::from_u64(2)],
vec![Counting::from_u64(3), Counting::from_u64(4)],
])
.unwrap();
let id = Matrix::identity(2);
assert_eq!(a.matmul(&id).unwrap(), a);
assert_eq!(id.matmul(&a).unwrap(), a);
}
}