use std::ops::{Add, AddAssign, Mul, Sub};
use crate::base_types::{c32, c64};
use crate::{BaseItem, FromAij, Shape};
use super::{
binary_operator::BinaryAijOperator, csr_mat::CsrMatrix, unary_aij_operator::UnaryAijOperator,
};
pub struct SparseMatOpIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
iter: I,
shape: [usize; 2],
}
pub struct ScalarMulIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
iter: I,
scalar: Item,
}
impl<Item, I> ScalarMulIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
pub fn new(iter: I, scalar: Item) -> Self {
Self { iter, scalar }
}
}
impl<Item, I> Iterator for ScalarMulIterator<Item, I>
where
Item: Copy + Default + Mul<Item, Output = Item>,
I: Iterator<Item = ([usize; 2], Item)>,
{
type Item = ([usize; 2], Item);
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(index, value)| (index, value * self.scalar))
}
}
impl<Item, I> SparseMatOpIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
pub fn new(iter: I, shape: [usize; 2]) -> Self {
Self { iter, shape }
}
pub fn iter(&self) -> &I {
&self.iter
}
pub fn map_stored<Out, F>(
self,
func: F,
) -> SparseMatOpIterator<Out, UnaryAijOperator<Item, I, Out, F>>
where
Out: Copy + Default + PartialEq,
F: Fn(Item) -> Out,
{
let shape = self.shape;
SparseMatOpIterator::new(UnaryAijOperator::new(self.iter, func), shape)
}
pub fn into_csr(self) -> CsrMatrix<Item>
where
Item: Copy + Default + PartialEq + AddAssign,
{
let shape = self.shape();
CsrMatrix::from_aij_iter(shape, self.iter)
}
}
impl<Item, I> Shape<2> for SparseMatOpIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
fn shape(&self) -> [usize; 2] {
self.shape
}
}
impl<Item, I> BaseItem for SparseMatOpIterator<Item, I>
where
Item: Copy + Default,
I: Iterator<Item = ([usize; 2], Item)>,
{
type Item = Item;
}
macro_rules! impl_unary_op {
($trait_name:ident, $method_name:ident) => {
impl<Item, I> crate::traits::number_traits::$trait_name for SparseMatOpIterator<Item, I>
where
Item: Copy + Default + crate::traits::number_traits::$trait_name,
<Item as crate::traits::number_traits::$trait_name>::Output: Copy + Default + PartialEq,
I: Iterator<Item = ([usize; 2], Item)>,
{
type Output = SparseMatOpIterator<
<Item as crate::traits::number_traits::$trait_name>::Output,
UnaryAijOperator<
Item,
I,
<Item as crate::traits::number_traits::$trait_name>::Output,
fn(Item) -> <Item as crate::traits::number_traits::$trait_name>::Output,
>,
>;
fn $method_name(self) -> Self::Output {
let shape = self.shape();
SparseMatOpIterator::new(
UnaryAijOperator::new(self.iter, |val: Item| val.$method_name()),
shape,
)
}
}
};
}
impl_unary_op!(Abs, abs);
impl_unary_op!(Square, square);
impl_unary_op!(AbsSquare, abs_square);
impl_unary_op!(Sqrt, sqrt);
impl_unary_op!(Sin, sin);
impl_unary_op!(Tan, tan);
impl_unary_op!(Asin, asin);
impl_unary_op!(Atan, atan);
impl_unary_op!(Sinh, sinh);
impl_unary_op!(Tanh, tanh);
impl_unary_op!(Asinh, asinh);
impl_unary_op!(Atanh, atanh);
impl<Item1, Item2, Out, I1, I2> std::ops::Add<SparseMatOpIterator<Item2, I2>>
for SparseMatOpIterator<Item1, I1>
where
Item1: Add<Item2, Output = Out> + Copy + Default,
Item2: Default + Copy,
Out: PartialEq + Default + Copy,
I1: Iterator<Item = ([usize; 2], Item1)>,
I2: Iterator<Item = ([usize; 2], Item2)>,
{
type Output = SparseMatOpIterator<
Out,
BinaryAijOperator<Item1, Item2, I1, I2, Out, fn(Item1, Item2) -> Out>,
>;
fn add(self, other: SparseMatOpIterator<Item2, I2>) -> Self::Output {
assert_eq!(self.shape(), other.shape());
let shape = self.shape();
SparseMatOpIterator::new(
BinaryAijOperator::new(self.iter, other.iter, |a, b| a + b),
shape,
)
}
}
impl<Item1, Item2, Out, I1, I2> std::ops::Sub<SparseMatOpIterator<Item2, I2>>
for SparseMatOpIterator<Item1, I1>
where
Item1: Sub<Item2, Output = Out> + Copy + Default,
Item2: Default + Copy,
Out: PartialEq + Default + Copy,
I1: Iterator<Item = ([usize; 2], Item1)>,
I2: Iterator<Item = ([usize; 2], Item2)>,
{
type Output = SparseMatOpIterator<
Out,
BinaryAijOperator<Item1, Item2, I1, I2, Out, fn(Item1, Item2) -> Out>,
>;
fn sub(self, other: SparseMatOpIterator<Item2, I2>) -> Self::Output {
assert_eq!(self.shape(), other.shape());
let shape = self.shape();
SparseMatOpIterator::new(
BinaryAijOperator::new(self.iter, other.iter, |a, b| a - b),
shape,
)
}
}
macro_rules! impl_scalar_mult {
($scalar_type:ty) => {
impl<I> std::ops::Mul<SparseMatOpIterator<$scalar_type, I>> for $scalar_type
where
I: Iterator<Item = ([usize; 2], $scalar_type)>,
{
type Output = SparseMatOpIterator<$scalar_type, ScalarMulIterator<$scalar_type, I>>;
fn mul(self, other: SparseMatOpIterator<$scalar_type, I>) -> Self::Output {
let shape = other.shape();
let new_iter = ScalarMulIterator::new(other.iter, self);
SparseMatOpIterator::new(new_iter, shape)
}
}
};
}
impl_scalar_mult!(f64);
impl_scalar_mult!(f32);
impl_scalar_mult!(c64);
impl_scalar_mult!(c32);
impl_scalar_mult!(usize);
impl_scalar_mult!(i8);
impl_scalar_mult!(i16);
impl_scalar_mult!(i32);
impl_scalar_mult!(i64);
impl_scalar_mult!(u8);
impl_scalar_mult!(u16);
impl_scalar_mult!(u32);
impl_scalar_mult!(u64);
impl<Item, I> std::ops::Mul<Item> for SparseMatOpIterator<Item, I>
where
Item: Copy + Default + Mul<Item, Output = Item>,
I: Iterator<Item = ([usize; 2], Item)>,
{
type Output = SparseMatOpIterator<Item, ScalarMulIterator<Item, I>>;
fn mul(self, scalar: Item) -> Self::Output {
let shape = self.shape();
let new_iter = ScalarMulIterator::new(self.iter, scalar);
SparseMatOpIterator::new(new_iter, shape)
}
}
impl<Item1, Item2, Out, I1, I2> std::ops::Mul<SparseMatOpIterator<Item2, I2>>
for SparseMatOpIterator<Item1, I1>
where
Item1: Mul<Item2, Output = Out> + Copy + Default,
Item2: Default + Copy,
Out: PartialEq + Default + Copy,
I1: Iterator<Item = ([usize; 2], Item1)>,
I2: Iterator<Item = ([usize; 2], Item2)>,
{
type Output = SparseMatOpIterator<
Out,
BinaryAijOperator<Item1, Item2, I1, I2, Out, fn(Item1, Item2) -> Out>,
>;
fn mul(self, other: SparseMatOpIterator<Item2, I2>) -> Self::Output {
assert_eq!(self.shape(), other.shape());
let shape = self.shape();
SparseMatOpIterator::new(
BinaryAijOperator::new(self.iter, other.iter, |a, b| a * b),
shape,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AijIteratorByValue, FromAij, Nonzeros};
#[test]
fn binary_operations_require_equal_shapes_in_both_orders() {
let a = CsrMatrix::from_aij([0, 2], &[], &[], &[] as &[f64]);
let b = CsrMatrix::from_aij([1, 2], &[], &[], &[] as &[f64]);
assert!(std::panic::catch_unwind(|| a.op() + b.op()).is_err());
assert!(std::panic::catch_unwind(|| b.op() + a.op()).is_err());
assert!(std::panic::catch_unwind(|| a.op() - b.op()).is_err());
assert!(std::panic::catch_unwind(|| a.op() * b.op()).is_err());
}
#[test]
fn map_stored_ignores_implicit_and_elides_mapped_zeros() {
let matrix = CsrMatrix::from_aij([2, 2], &[0, 0, 1], &[0, 1, 1], &[0.0, 2.0, 3.0]);
assert_eq!(matrix.nnz(), 2);
let exponential = matrix.op().map_stored(f64::exp).into_csr();
assert_eq!(exponential.nnz(), 2);
assert_eq!(
exponential.iter_aij_value().collect::<Vec<_>>(),
vec![([0, 1], 2.0_f64.exp()), ([1, 1], 3.0_f64.exp())]
);
assert_eq!(exponential.todense().data().unwrap()[0], 0.0);
let elided = matrix
.op()
.map_stored(|value| if value == 2.0 { 0.0 } else { value })
.into_csr();
assert_eq!(elided.nnz(), 1);
assert_eq!(
elided.iter_aij_value().collect::<Vec<_>>(),
vec![([1, 1], 3.0)]
);
}
#[test]
fn map_stored_filters_many_entries_without_recursion() {
let count = 100_000;
let rows = (0..count).collect::<Vec<_>>();
let cols = vec![0; count];
let values = vec![1.0; count];
let matrix = CsrMatrix::from_aij([count, 1], &rows, &cols, &values);
let mapped = matrix.op().map_stored(|_| 0.0).into_csr();
assert_eq!(mapped.nnz(), 0);
}
}