use std::ops::{Index, IndexMut};
use ironlab_ir::IrError;
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix {
rows: usize,
cols: usize,
values: Vec<f64>,
}
impl Matrix {
#[must_use]
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
values: vec![0.0; rows * cols],
}
}
#[must_use]
pub fn from_fn(rows: usize, cols: usize, mut f: impl FnMut(usize, usize) -> f64) -> Self {
let values = (0..rows)
.flat_map(|row| (0..cols).map(move |col| (row, col)))
.map(|(row, col)| f(row, col))
.collect();
Self { rows, cols, values }
}
#[must_use]
pub fn from_rows<R: AsRef<[f64]>>(rows: &[R]) -> Self {
let cols = rows.first().map_or(0, |row| row.as_ref().len());
assert!(
rows.iter().all(|row| row.as_ref().len() == cols),
"matrix rows have different lengths"
);
Self {
rows: rows.len(),
cols,
values: rows.iter().flat_map(|row| row.as_ref()).copied().collect(),
}
}
pub fn from_vec(rows: usize, cols: usize, values: Vec<f64>) -> Result<Self, IrError> {
if rows.checked_mul(cols) == Some(values.len()) {
Ok(Self { rows, cols, values })
} else {
Err(IrError::InvalidShape {
shape: vec![rows, cols],
len: values.len(),
})
}
}
#[must_use]
pub fn rows(&self) -> usize {
self.rows
}
#[must_use]
pub fn cols(&self) -> usize {
self.cols
}
#[must_use]
pub fn values(&self) -> &[f64] {
&self.values
}
#[must_use]
pub fn row(&self, row: usize) -> &[f64] {
assert!(
row < self.rows,
"row {row} out of range for a matrix of {} rows",
self.rows
);
let start = row * self.cols;
&self.values[start..start + self.cols]
}
#[must_use]
pub fn map(&self, mut f: impl FnMut(f64) -> f64) -> Self {
Self {
rows: self.rows,
cols: self.cols,
values: self.values.iter().map(|&v| f(v)).collect(),
}
}
#[must_use]
pub fn zip_map(&self, other: &Matrix, mut f: impl FnMut(f64, f64) -> f64) -> Self {
assert!(
(self.rows, self.cols) == (other.rows, other.cols),
"matrices have different shapes: {}x{} and {}x{}",
self.rows,
self.cols,
other.rows,
other.cols
);
Self {
rows: self.rows,
cols: self.cols,
values: self
.values
.iter()
.zip(&other.values)
.map(|(&a, &b)| f(a, b))
.collect(),
}
}
#[must_use]
pub fn into_values(self) -> Vec<f64> {
self.values
}
fn flat_index(&self, row: usize, col: usize) -> usize {
assert!(
row < self.rows && col < self.cols,
"index ({row}, {col}) out of range for a {}x{} matrix",
self.rows,
self.cols
);
row * self.cols + col
}
}
impl Index<(usize, usize)> for Matrix {
type Output = f64;
fn index(&self, (row, col): (usize, usize)) -> &f64 {
&self.values[self.flat_index(row, col)]
}
}
impl IndexMut<(usize, usize)> for Matrix {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut f64 {
let index = self.flat_index(row, col);
&mut self.values[index]
}
}
#[must_use]
pub fn linspace(start: f64, end: f64, n: usize) -> Vec<f64> {
match n {
0 => Vec::new(),
1 => vec![end],
_ => {
let intervals = (n - 1) as f64;
let step = (end - start) / intervals;
let mut values: Vec<f64> = (0..n).map(|i| start + i as f64 * step).collect();
values[n - 1] = end;
values
}
}
}
#[must_use]
pub fn logspace(start_exp: f64, end_exp: f64, n: usize) -> Vec<f64> {
linspace(start_exp, end_exp, n)
.into_iter()
.map(|exponent| {
if exponent.fract() == 0.0 && exponent.abs() <= f64::from(i32::MAX) {
10f64.powi(exponent as i32)
} else {
10f64.powf(exponent)
}
})
.collect()
}
#[must_use]
pub fn meshgrid(x: &[f64], y: &[f64]) -> (Matrix, Matrix) {
(
Matrix::from_fn(y.len(), x.len(), |_, col| x[col]),
Matrix::from_fn(y.len(), x.len(), |row, _| y[row]),
)
}