use crate::linalg::{Matrix, Vector};
use crate::Num;
use rayon::iter::IntoParallelRefMutIterator;
use rayon::prelude::*;
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
pub fn mtrxdot<T: Num>(a: &Matrix<T>, b: &Matrix<T>) -> Matrix<T>
{
assert_eq!(
a.cols, b.rows,
"Matrix multiplication shape mismatch: left = ({},{}) right = ({},{})",
a.rows, a.cols, b.rows, b.cols
);
let rows = a.rows;
let cols = b.cols;
let k = a.cols;
let small_threshold = 19 * 19;
let mut rhs_t = vec![T::default(); cols * k];
for p in 0..k {
for j in 0..cols {
rhs_t[j * k + p] = b.data[p * b.cols + j];
}
}
let mut data = vec![T::default(); rows * cols];
if rows * cols <= small_threshold {
for i in 0..rows {
let a_row_base = i * a.cols;
let out_row_base = i * cols;
for j in 0..cols {
let mut sum = T::default();
let rt_base = j * k;
for p in 0..k {
sum += a.data[a_row_base + p] * rhs_t[rt_base + p];
}
data[out_row_base + j] = sum;
}
}
} else {
data.par_chunks_mut(cols).enumerate().for_each(|(i, out_row)| {
let a_row_base = i * a.cols;
for j in 0..cols {
let mut sum = T::default();
let rt_base = j * k;
for p in 0..k {
sum += a.data[a_row_base + p] * rhs_t[rt_base + p];
}
out_row[j] = sum;
}
});
}
Matrix::new(data, rows, cols)
}
impl<T: Num> Neg for Matrix<T> {
type Output = Matrix<T>;
fn neg(self) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = -self.data[i];
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> Add<&Matrix<T>> for Matrix<T> {
type Output = Matrix<T>;
fn add(self, rhs: &Matrix<T>) -> Self::Output {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nCannot add Matrix 1: {:?} and Matrix 2: {:?}",
self.shape(),
rhs.shape()
);
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] + rhs.data[i];
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> Add<&Matrix<T>> for &Matrix<T> {
type Output = Matrix<T>;
fn add(self, rhs: &Matrix<T>) -> Self::Output {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nCannot add Matrix 1: {:?} and Matrix 2: {:?}",
self.shape(),
rhs.shape()
);
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] + rhs.data[i];
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> AddAssign<&Matrix<T>> for Matrix<T> {
fn add_assign(&mut self, rhs: &Matrix<T>) {
assert!(self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nCannot add assign Matrix 1: {:?} and Matrix 2: {:?}",
self.shape(), rhs.shape());
self.data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x += rhs.data[i];
});
}
}
impl<T: Num> Add<T> for Matrix<T> {
type Output = Matrix<T>;
fn add(self, rhs: T) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] + rhs;
});
Matrix::new(data, self.rows, self.cols)
}
}
macro_rules! impl_add_for_types {
($($type:ty),*) => {
$(
impl Add<Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn add(self, rhs: Matrix<$type>) -> Matrix<$type> {
rhs + self
}
}
impl Add<&Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn add(self, rhs: &Matrix<$type>) -> Matrix<$type> {
rhs.map(| x | x + self)
}
}
)*
};
}
impl_add_for_types!(i16, i32, i64, i128, f32, f64);
impl<T: Num> AddAssign<T> for Matrix<T> {
fn add_assign(&mut self, rhs: T) {
self.data.par_iter_mut().for_each(|x| {
*x += rhs;
});
}
}
impl<T: Num> Sub<&Matrix<T>> for Matrix<T> {
type Output = Matrix<T>;
fn sub(self, rhs: &Matrix<T>) -> Self::Output {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nMatrix 1: [{}, {}], Matrix 2: [{} {}]",
self.rows,
self.cols,
rhs.rows,
rhs.cols
);
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] - rhs.data[i];
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> Sub<&Matrix<T>> for &Matrix<T> {
type Output = Matrix<T>;
fn sub(self, rhs: &Matrix<T>) -> Self::Output {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nMatrix 1: [{}, {}], Matrix 2: [{} {}]",
self.rows,
self.cols,
rhs.rows,
rhs.cols
);
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] - rhs.data[i];
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> SubAssign<&Matrix<T>> for Matrix<T> {
fn sub_assign(&mut self, rhs: &Matrix<T>) {
if rhs.rows == 1 && rhs.cols == 1 {
*self -= rhs.data[0];
return;
}
assert!(self.rows == rhs.rows && self.cols == rhs.cols,
"!!!Matrix dimensions do not match!!!\nCannot sub-assign Matrix 1: {:?} and Matrix 2: {:?}",
self.shape(), rhs.shape());
self.data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x -= rhs.data[i];
});
}
}
impl<T: Num> Sub<T> for Matrix<T> {
type Output = Matrix<T>;
fn sub(self, rhs: T) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] - rhs;
});
Matrix::new(data, self.rows, self.cols)
}
}
macro_rules! impl_sub_for_types {
($($type:ty),*) => {
$(
impl Sub<Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn sub(self, rhs: Matrix<$type>) -> Matrix<$type> {
let mut data = vec![self; rhs.rows * rhs.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self - rhs.data[i];
});
Matrix::new(data, rhs.rows, rhs.cols)
}
}
impl Sub<&Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn sub(self, rhs: &Matrix<$type>) -> Matrix<$type> {
let mut data = vec![self; rhs.rows * rhs.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self - rhs.data[i];
});
Matrix::new(data, rhs.rows, rhs.cols)
}
}
)*
};
}
impl_sub_for_types!(i16, i32, i64, i128, f32, f64);
impl<T: Num> SubAssign<T> for Matrix<T> {
fn sub_assign(&mut self, rhs: T) {
self.data.par_iter_mut().for_each(|x| *x -= rhs);
}
}
impl<T: Num> Mul<&Vector<T>> for Matrix<T> {
type Output = Vector<T>;
fn mul(self, rhs: &Vector<T>) -> Self::Output {
assert_eq!(
self.cols, rhs.length,
"!!!Matrix amount of columns != Vector length!!!\n\
Matrix cols: {}, Vector length: {}!!!",
self.cols, rhs.length
);
let mut data = vec![T::default(); self.rows];
data.par_iter_mut().enumerate().for_each(|(index, x)| {
for i in 0..self.cols {
*x += self[[index, i]] * rhs[i];
}
});
Vector::from(data)
}
}
impl<T: Num> Mul<&Vector<T>> for &Matrix<T> {
type Output = Vector<T>;
fn mul(self, rhs: &Vector<T>) -> Self::Output {
assert_eq!(
self.cols, rhs.length,
"!!!Matrix amount of columns != Vector length!!!\n\
Matrix cols: {}, Vector length: {}!!!",
self.cols, rhs.length
);
let mut data = vec![T::default(); self.rows];
data.par_iter_mut().enumerate().for_each(|(index, x)| {
for i in 0..self.cols {
*x += self[[index, i]] * rhs[i];
}
});
Vector::from(data)
}
}
impl<T: Num> Mul<&Matrix<T>> for Matrix<T> {
type Output = Matrix<T>;
fn mul(self, rhs: &Matrix<T>) -> Self::Output {
if rhs.rows == 1 && rhs.cols == 1 {
return self * rhs.data[0];
}
assert_eq!(self.cols, rhs.rows,
"!!!Matrix amount of columns in the 1st matrix != amount of rows in the 2nd matrix!!!\n\
Matrix shape: {:?}, Other Matrix shape: {:?}\nCan't multiply", self.shape(), rhs.shape());
mtrxdot(&self, rhs)
}
}
impl<T: Num> Mul<&Matrix<T>> for &Matrix<T> {
type Output = Matrix<T>;
fn mul(self, rhs: &Matrix<T>) -> Self::Output {
assert_eq!(self.cols, rhs.rows, "!!!Matrix amount of columns in the 1st matrix != amount of rows in the 2nd matrix!!!\n\
Matrix shape: {:?}, Other Matrix shape: {:?}\nCan't multiply", self.shape(), rhs.shape());
mtrxdot(self, rhs)
}
}
impl<T: Num> MulAssign<&Matrix<T>> for Matrix<T> {
fn mul_assign(&mut self, rhs: &Matrix<T>) {
assert_eq!(
self.cols, rhs.rows,
"!!!Matrix amount of columns in the 1st matrix \
does not equal the amount of rows in the 2nd matrix!!!\nCan't multiply"
);
*self = mtrxdot(&self, rhs) }
}
impl<T: Num> BitAnd<&Matrix<T>> for &Matrix<T> {
type Output = Matrix<T>;
fn bitand(self, rhs: &Matrix<T>) -> Self::Output {
self.hadamard(rhs)
}
}
impl<T: Num> BitAnd<Matrix<T>> for Matrix<T> {
type Output = Matrix<T>;
fn bitand(self, rhs: Matrix<T>) -> Self::Output {
self.hadamard(&rhs)
}
}
impl<T: Num> BitAnd<&Matrix<T>> for Matrix<T> {
type Output = Matrix<T>;
fn bitand(self, rhs: &Matrix<T>) -> Self::Output {
self.hadamard(rhs)
}
}
impl<T: Num> BitAndAssign<Matrix<T>> for Matrix<T> {
fn bitand_assign(&mut self, rhs: Matrix<T>) {
*self = self.hadamard(&rhs);
}
}
impl<T: Num> BitAndAssign<&Matrix<T>> for Matrix<T> {
fn bitand_assign(&mut self, rhs: &Matrix<T>) {
*self = self.hadamard(rhs);
}
}
impl<T: Num> Mul<T> for Matrix<T> {
type Output = Matrix<T>;
fn mul(self, rhs: T) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] * rhs;
});
Matrix::new(data, self.rows, self.cols)
}
}
macro_rules! impl_mul_for_types {
($($type:ty),*) => {
$(
impl Mul<Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn mul(self, matrix: Matrix<$type>) -> Matrix<$type> {
matrix * self
}
}
impl Mul<&Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn mul(self, matrix: &Matrix<$type>) -> Matrix<$type> {
matrix.clone() * self
}
}
)*
};
}
impl_mul_for_types!(i16, i32, i64, i128, f32, f64);
impl<T: Num> MulAssign<T> for Matrix<T> {
fn mul_assign(&mut self, rhs: T) {
self.data.par_iter_mut().for_each(|x| {
*x = *x * rhs;
});
}
}
impl<T: Num> Div<T> for Matrix<T> {
type Output = Matrix<T>;
fn div(self, rhs: T) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] / rhs;
});
Matrix::new(data, self.rows, self.cols)
}
}
impl<T: Num> Div<T> for &Matrix<T> {
type Output = Matrix<T>;
fn div(self, rhs: T) -> Self::Output {
let mut data = vec![T::default(); self.rows * self.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self.data[i] / rhs;
});
Matrix::new(data, self.rows, self.cols)
}
}
macro_rules! impl_div_for_types {
($($type:ty),*) => {
$(
impl Div<Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn div(self, rhs: Matrix<$type>) -> Matrix<$type> {
let mut data = vec![self; rhs.rows * rhs.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self / rhs.data[i];
});
Matrix::new(data, rhs.rows, rhs.cols)
}
}
impl Div<&Matrix<$type>> for $type {
type Output = Matrix<$type>;
fn div(self, rhs: &Matrix<$type>) -> Matrix<$type> {
let mut data = vec![self; rhs.rows * rhs.cols];
data.par_iter_mut().enumerate().for_each(|(i, x)| {
*x = self / rhs.data[i];
});
Matrix::new(data, rhs.rows, rhs.cols)
}
}
)*
};
}
impl_div_for_types!(i16, i32, i64, i128, f32, f64);
impl<T:Num> DivAssign<T> for Matrix<T> {
fn div_assign(&mut self, rhs: T) {
self.data.par_iter_mut().for_each(|x| {*x = *x / rhs});
}
}
#[cfg(test)]
mod tests {
use crate::linalg::Matrix;
#[test]
fn mx_multiplication_test() {
use std::time::Instant;
use rayon::prelude::*;
fn measure_avg<F: Fn() -> Matrix<f32>>(f: F, runs: usize) -> std::time::Duration {
let _ = f();
let mut total = std::time::Duration::ZERO;
for _ in 0..runs {
let t0 = Instant::now();
let _res = f();
total += t0.elapsed();
}
total / (runs as u32)
}
fn mtrxdot_ref_st(a: &Matrix<f32>, b: &Matrix<f32>) -> Matrix<f32> {
assert_eq!(a.cols, b.rows);
let rows = a.rows;
let cols = b.cols;
let k = a.cols;
let mut rhs_t = vec![0.0f32; cols * k];
for p in 0..k {
for j in 0..cols {
rhs_t[j * k + p] = b.data[p * b.cols + j];
}
}
let mut data = vec![0.0f32; rows * cols];
for i in 0..rows {
let a_row_base = i * a.cols;
let out_row_base = i * cols;
for j in 0..cols {
let mut sum = 0.0f32;
let rt_base = j * k;
for p in 0..k {
sum += a.data[a_row_base + p] * rhs_t[rt_base + p];
}
data[out_row_base + j] = sum;
}
}
Matrix::new(data, rows, cols)
}
fn mtrxdot_ref_par(a: &Matrix<f32>, b: &Matrix<f32>) -> Matrix<f32> {
assert_eq!(a.cols, b.rows);
let rows = a.rows;
let cols = b.cols;
let k = a.cols;
let mut rhs_t = vec![0.0f32; cols * k];
for p in 0..k {
for j in 0..cols {
rhs_t[j * k + p] = b.data[p * b.cols + j];
}
}
let mut data = vec![0.0f32; rows * cols];
data.par_chunks_mut(cols).enumerate().for_each(|(i, out_row)| {
let a_row_base = i * a.cols;
for j in 0..cols {
let mut sum = 0.0f32;
let rt_base = j * k;
for p in 0..k {
sum += a.data[a_row_base + p] * rhs_t[rt_base + p];
}
out_row[j] = sum;
}
});
Matrix::new(data, rows, cols)
}
let runs = 3usize;
println!("n, lib_us, st_us, par_us, rayon_threads");
for n in 5usize..128 {
let a = Matrix::from_num((n as f32).powf(2.5), n, n);
let b = Matrix::from_num(1.0f32, n, n);
let avg_lib = std::panic::catch_unwind(|| {
measure_avg(|| crate::linalg::matrix_ops::mtrxdot(&a, &b), runs)
});
let avg_st = measure_avg(|| mtrxdot_ref_st(&a, &b), runs);
let avg_par = measure_avg(|| mtrxdot_ref_par(&a, &b), runs);
let rayon_threads = std::env::var("RAYON_NUM_THREADS").unwrap_or_else(|_| "auto".into());
match avg_lib {
Ok(dur) => println!("{},{},{},{},{}", n, dur.as_micros(), avg_st.as_micros(), avg_par.as_micros(), rayon_threads),
Err(_) => println!("{},{},{},{},{}", n, "na", avg_st.as_micros(), avg_par.as_micros(), rayon_threads),
}
}
}
}