2020-12-16 21:06:12 +08:00
|
|
|
use crate::common::{csr_strategy, PROPTEST_MATRIX_DIM, PROPTEST_MAX_NNZ,
|
|
|
|
PROPTEST_I32_VALUE_STRATEGY};
|
2020-12-21 23:05:38 +08:00
|
|
|
use nalgebra_sparse::ops::serial::{spmm_csr_dense, spadd_pattern, spmm_pattern, spadd_csr_prealloc, spmm_csr_prealloc};
|
2020-12-21 22:09:29 +08:00
|
|
|
use nalgebra_sparse::ops::{Op};
|
2020-12-02 23:56:22 +08:00
|
|
|
use nalgebra_sparse::csr::CsrMatrix;
|
2020-12-04 21:13:07 +08:00
|
|
|
use nalgebra_sparse::proptest::{csr, sparsity_pattern};
|
|
|
|
use nalgebra_sparse::pattern::SparsityPattern;
|
2020-12-02 23:56:22 +08:00
|
|
|
|
2020-12-14 18:18:16 +08:00
|
|
|
use nalgebra::{DMatrix, Scalar, DMatrixSliceMut, DMatrixSlice};
|
2020-12-02 23:56:22 +08:00
|
|
|
use nalgebra::proptest::matrix;
|
|
|
|
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use std::panic::catch_unwind;
|
2020-12-04 21:13:07 +08:00
|
|
|
use std::sync::Arc;
|
2020-07-14 00:44:40 +08:00
|
|
|
|
2020-12-10 20:30:37 +08:00
|
|
|
/// Represents the sparsity pattern of a CSR matrix as a dense matrix with 0/1
|
|
|
|
fn dense_csr_pattern(pattern: &SparsityPattern) -> DMatrix<i32> {
|
|
|
|
let boolean_csr = CsrMatrix::try_from_pattern_and_values(
|
|
|
|
Arc::new(pattern.clone()),
|
|
|
|
vec![1; pattern.nnz()])
|
|
|
|
.unwrap();
|
|
|
|
DMatrix::from(&boolean_csr)
|
|
|
|
}
|
|
|
|
|
2020-12-02 23:56:22 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct SpmmCsrDenseArgs<T: Scalar> {
|
|
|
|
c: DMatrix<T>,
|
|
|
|
beta: T,
|
|
|
|
alpha: T,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: Op<CsrMatrix<T>>,
|
|
|
|
b: Op<DMatrix<T>>,
|
2020-12-02 23:56:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns matrices C, A and B with compatible dimensions such that it can be used
|
|
|
|
/// in an `spmm` operation `C = beta * C + alpha * trans(A) * trans(B)`.
|
|
|
|
fn spmm_csr_dense_args_strategy() -> impl Strategy<Value=SpmmCsrDenseArgs<i32>> {
|
2020-12-16 21:06:12 +08:00
|
|
|
let max_nnz = PROPTEST_MAX_NNZ;
|
|
|
|
let value_strategy = PROPTEST_I32_VALUE_STRATEGY;
|
|
|
|
let c_rows = PROPTEST_MATRIX_DIM;
|
|
|
|
let c_cols = PROPTEST_MATRIX_DIM;
|
|
|
|
let common_dim = PROPTEST_MATRIX_DIM;
|
2020-12-02 23:56:22 +08:00
|
|
|
let trans_strategy = trans_strategy();
|
|
|
|
let c_matrix_strategy = matrix(value_strategy.clone(), c_rows, c_cols);
|
|
|
|
|
|
|
|
(c_matrix_strategy, common_dim, trans_strategy.clone(), trans_strategy.clone())
|
|
|
|
.prop_flat_map(move |(c, common_dim, trans_a, trans_b)| {
|
|
|
|
let a_shape =
|
2020-12-21 22:09:29 +08:00
|
|
|
if trans_a { (common_dim, c.nrows()) }
|
2020-12-02 23:56:22 +08:00
|
|
|
else { (c.nrows(), common_dim) };
|
|
|
|
let b_shape =
|
2020-12-21 22:09:29 +08:00
|
|
|
if trans_b { (c.ncols(), common_dim) }
|
2020-12-02 23:56:22 +08:00
|
|
|
else { (common_dim, c.ncols()) };
|
|
|
|
let a = csr(value_strategy.clone(), Just(a_shape.0), Just(a_shape.1), max_nnz);
|
|
|
|
let b = matrix(value_strategy.clone(), b_shape.0, b_shape.1);
|
|
|
|
|
|
|
|
// We use the same values for alpha, beta parameters as for matrix elements
|
|
|
|
let alpha = value_strategy.clone();
|
|
|
|
let beta = value_strategy.clone();
|
|
|
|
|
|
|
|
(Just(c), beta, alpha, Just(trans_a), a, Just(trans_b), b)
|
|
|
|
}).prop_map(|(c, beta, alpha, trans_a, a, trans_b, b)| {
|
|
|
|
SpmmCsrDenseArgs {
|
|
|
|
c,
|
|
|
|
beta,
|
|
|
|
alpha,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: if trans_a { Op::Transpose(a) } else { Op::NoOp(a) },
|
|
|
|
b: if trans_b { Op::Transpose(b) } else { Op::NoOp(b) },
|
2020-12-02 23:56:22 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-12-10 20:30:37 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct SpaddCsrArgs<T> {
|
|
|
|
c: CsrMatrix<T>,
|
|
|
|
beta: T,
|
|
|
|
alpha: T,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: Op<CsrMatrix<T>>,
|
2020-12-10 20:30:37 +08:00
|
|
|
}
|
|
|
|
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spadd_csr_prealloc_args_strategy() -> impl Strategy<Value=SpaddCsrArgs<i32>> {
|
2020-12-16 21:06:12 +08:00
|
|
|
let value_strategy = PROPTEST_I32_VALUE_STRATEGY;
|
2020-12-10 20:30:37 +08:00
|
|
|
|
2020-12-21 22:09:29 +08:00
|
|
|
spadd_pattern_strategy()
|
2020-12-10 20:30:37 +08:00
|
|
|
.prop_flat_map(move |(a_pattern, b_pattern)| {
|
2020-12-17 00:30:48 +08:00
|
|
|
let c_pattern = spadd_pattern(&a_pattern, &b_pattern);
|
2020-12-10 20:30:37 +08:00
|
|
|
|
|
|
|
let a_values = vec![value_strategy.clone(); a_pattern.nnz()];
|
|
|
|
let c_values = vec![value_strategy.clone(); c_pattern.nnz()];
|
|
|
|
let alpha = value_strategy.clone();
|
|
|
|
let beta = value_strategy.clone();
|
|
|
|
(Just(c_pattern), Just(a_pattern), c_values, a_values, alpha, beta, trans_strategy())
|
|
|
|
}).prop_map(|(c_pattern, a_pattern, c_values, a_values, alpha, beta, trans_a)| {
|
|
|
|
let c = CsrMatrix::try_from_pattern_and_values(Arc::new(c_pattern), c_values).unwrap();
|
|
|
|
let a = CsrMatrix::try_from_pattern_and_values(Arc::new(a_pattern), a_values).unwrap();
|
|
|
|
|
2020-12-21 22:09:29 +08:00
|
|
|
let a = if trans_a { Op::Transpose(a.transpose()) } else { Op::NoOp(a) };
|
|
|
|
SpaddCsrArgs { c, beta, alpha, a }
|
2020-12-10 20:30:37 +08:00
|
|
|
})
|
|
|
|
}
|
2020-12-02 23:56:22 +08:00
|
|
|
|
|
|
|
fn dense_strategy() -> impl Strategy<Value=DMatrix<i32>> {
|
2020-12-16 21:06:12 +08:00
|
|
|
matrix(PROPTEST_I32_VALUE_STRATEGY, PROPTEST_MATRIX_DIM, PROPTEST_MATRIX_DIM)
|
2020-12-02 23:56:22 +08:00
|
|
|
}
|
|
|
|
|
2020-12-21 22:09:29 +08:00
|
|
|
fn trans_strategy() -> impl Strategy<Value=bool> + Clone {
|
|
|
|
proptest::bool::ANY
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wraps the values of the given strategy in `Op`, producing both transposed and non-transposed
|
|
|
|
/// values.
|
|
|
|
fn op_strategy<S: Strategy>(strategy: S) -> impl Strategy<Value=Op<S::Value>> {
|
|
|
|
let is_transposed = proptest::bool::ANY;
|
|
|
|
(strategy, is_transposed)
|
|
|
|
.prop_map(|(obj, is_trans)| if is_trans {
|
|
|
|
Op::Transpose(obj)
|
|
|
|
} else {
|
|
|
|
Op::NoOp(obj)
|
|
|
|
})
|
2020-12-02 23:56:22 +08:00
|
|
|
}
|
|
|
|
|
2020-12-04 21:13:07 +08:00
|
|
|
fn pattern_strategy() -> impl Strategy<Value=SparsityPattern> {
|
2020-12-16 21:06:12 +08:00
|
|
|
sparsity_pattern(PROPTEST_MATRIX_DIM, PROPTEST_MATRIX_DIM, PROPTEST_MAX_NNZ)
|
2020-12-04 21:13:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs pairs (a, b) where a and b have the same dimensions
|
2020-12-21 22:09:29 +08:00
|
|
|
fn spadd_pattern_strategy() -> impl Strategy<Value=(SparsityPattern, SparsityPattern)> {
|
2020-12-04 21:13:07 +08:00
|
|
|
pattern_strategy()
|
|
|
|
.prop_flat_map(|a| {
|
2020-12-16 21:06:12 +08:00
|
|
|
let b = sparsity_pattern(Just(a.major_dim()), Just(a.minor_dim()), PROPTEST_MAX_NNZ);
|
2020-12-04 21:13:07 +08:00
|
|
|
(Just(a), b)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-12-14 23:55:06 +08:00
|
|
|
/// Constructs pairs (a, b) where a and b have compatible dimensions for a matrix product
|
|
|
|
fn spmm_pattern_strategy() -> impl Strategy<Value=(SparsityPattern, SparsityPattern)> {
|
|
|
|
pattern_strategy()
|
|
|
|
.prop_flat_map(|a| {
|
|
|
|
let b = sparsity_pattern(Just(a.minor_dim()), PROPTEST_MATRIX_DIM, PROPTEST_MAX_NNZ);
|
|
|
|
(Just(a), b)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-12-16 21:06:12 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct SpmmCsrArgs<T> {
|
|
|
|
c: CsrMatrix<T>,
|
|
|
|
beta: T,
|
|
|
|
alpha: T,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: Op<CsrMatrix<T>>,
|
|
|
|
b: Op<CsrMatrix<T>>,
|
2020-12-16 21:06:12 +08:00
|
|
|
}
|
|
|
|
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spmm_csr_prealloc_args_strategy() -> impl Strategy<Value=SpmmCsrArgs<i32>> {
|
2020-12-16 21:06:12 +08:00
|
|
|
spmm_pattern_strategy()
|
|
|
|
.prop_flat_map(|(a_pattern, b_pattern)| {
|
|
|
|
let a_values = vec![PROPTEST_I32_VALUE_STRATEGY; a_pattern.nnz()];
|
|
|
|
let b_values = vec![PROPTEST_I32_VALUE_STRATEGY; b_pattern.nnz()];
|
|
|
|
let c_pattern = spmm_pattern(&a_pattern, &b_pattern);
|
|
|
|
let c_values = vec![PROPTEST_I32_VALUE_STRATEGY; c_pattern.nnz()];
|
|
|
|
let a_pattern = Arc::new(a_pattern);
|
|
|
|
let b_pattern = Arc::new(b_pattern);
|
|
|
|
let c_pattern = Arc::new(c_pattern);
|
|
|
|
let a = a_values.prop_map(move |values|
|
|
|
|
CsrMatrix::try_from_pattern_and_values(Arc::clone(&a_pattern), values).unwrap());
|
|
|
|
let b = b_values.prop_map(move |values|
|
|
|
|
CsrMatrix::try_from_pattern_and_values(Arc::clone(&b_pattern), values).unwrap());
|
|
|
|
let c = c_values.prop_map(move |values|
|
|
|
|
CsrMatrix::try_from_pattern_and_values(Arc::clone(&c_pattern), values).unwrap());
|
|
|
|
let alpha = PROPTEST_I32_VALUE_STRATEGY;
|
|
|
|
let beta = PROPTEST_I32_VALUE_STRATEGY;
|
|
|
|
(c, beta, alpha, trans_strategy(), a, trans_strategy(), b)
|
|
|
|
})
|
|
|
|
.prop_map(|(c, beta, alpha, trans_a, a, trans_b, b)| {
|
|
|
|
SpmmCsrArgs::<i32> {
|
|
|
|
c,
|
|
|
|
beta,
|
|
|
|
alpha,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: if trans_a { Op::Transpose(a.transpose()) } else { Op::NoOp(a) },
|
|
|
|
b: if trans_b { Op::Transpose(b.transpose()) } else { Op::NoOp(b) }
|
2020-12-16 21:06:12 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-12-21 22:42:32 +08:00
|
|
|
/// Helper function to help us call dense GEMM with our `Op` type
|
|
|
|
fn dense_gemm<'a>(beta: i32,
|
|
|
|
c: impl Into<DMatrixSliceMut<'a, i32>>,
|
2020-12-02 23:56:22 +08:00
|
|
|
alpha: i32,
|
2020-12-21 22:09:29 +08:00
|
|
|
a: Op<impl Into<DMatrixSlice<'a, i32>>>,
|
|
|
|
b: Op<impl Into<DMatrixSlice<'a, i32>>>)
|
2020-12-02 23:56:22 +08:00
|
|
|
{
|
|
|
|
let mut c = c.into();
|
2020-12-21 22:09:29 +08:00
|
|
|
let a = a.convert();
|
|
|
|
let b = b.convert();
|
|
|
|
|
|
|
|
use Op::{NoOp, Transpose};
|
|
|
|
match (a, b) {
|
|
|
|
(NoOp(a), NoOp(b)) => c.gemm(alpha, &a, &b, beta),
|
|
|
|
(Transpose(a), NoOp(b)) => c.gemm(alpha, &a.transpose(), &b, beta),
|
|
|
|
(NoOp(a), Transpose(b)) => c.gemm(alpha, &a, &b.transpose(), beta),
|
|
|
|
(Transpose(a), Transpose(b)) => c.gemm(alpha, &a.transpose(), &b.transpose(), beta)
|
|
|
|
}
|
2020-12-02 23:56:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
proptest! {
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn spmm_csr_dense_agrees_with_dense_result(
|
2020-12-21 22:09:29 +08:00
|
|
|
SpmmCsrDenseArgs { c, beta, alpha, a, b }
|
2020-12-02 23:56:22 +08:00
|
|
|
in spmm_csr_dense_args_strategy()
|
|
|
|
) {
|
|
|
|
let mut spmm_result = c.clone();
|
2020-12-21 22:42:32 +08:00
|
|
|
spmm_csr_dense(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref());
|
2020-12-02 23:56:22 +08:00
|
|
|
|
|
|
|
let mut gemm_result = c.clone();
|
2020-12-21 22:09:29 +08:00
|
|
|
let a_dense = a.map_same_op(|a| DMatrix::from(&a));
|
2020-12-21 22:42:32 +08:00
|
|
|
dense_gemm(beta, &mut gemm_result, alpha, a_dense.as_ref(), b.as_ref());
|
2020-12-02 23:56:22 +08:00
|
|
|
|
|
|
|
prop_assert_eq!(spmm_result, gemm_result);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn spmm_csr_dense_panics_on_dim_mismatch(
|
2020-12-21 22:09:29 +08:00
|
|
|
(alpha, beta, c, a, b)
|
|
|
|
in (PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
dense_strategy(),
|
|
|
|
op_strategy(csr_strategy()),
|
|
|
|
op_strategy(dense_strategy()))
|
2020-12-02 23:56:22 +08:00
|
|
|
) {
|
|
|
|
// We refer to `A * B` as the "product"
|
2020-12-21 22:09:29 +08:00
|
|
|
let product_rows = match &a {
|
|
|
|
Op::NoOp(ref a) => a.nrows(),
|
|
|
|
Op::Transpose(ref a) => a.ncols(),
|
|
|
|
};
|
|
|
|
let product_cols = match &b {
|
|
|
|
Op::NoOp(ref b) => b.ncols(),
|
|
|
|
Op::Transpose(ref b) => b.nrows(),
|
|
|
|
};
|
2020-12-02 23:56:22 +08:00
|
|
|
// Determine the common dimension in the product
|
|
|
|
// from the perspective of a and b, respectively
|
2020-12-21 22:09:29 +08:00
|
|
|
let product_a_common = match &a {
|
|
|
|
Op::NoOp(ref a) => a.ncols(),
|
|
|
|
Op::Transpose(ref a) => a.nrows(),
|
|
|
|
};
|
|
|
|
let product_b_common = match &b {
|
|
|
|
Op::NoOp(ref b) => b.nrows(),
|
|
|
|
Op::Transpose(ref b) => b.ncols()
|
|
|
|
};
|
2020-12-02 23:56:22 +08:00
|
|
|
|
|
|
|
let dims_are_compatible = product_rows == c.nrows()
|
|
|
|
&& product_cols == c.ncols()
|
|
|
|
&& product_a_common == product_b_common;
|
|
|
|
|
|
|
|
// If the dimensions randomly happen to be compatible, then of course we need to
|
|
|
|
// skip the test, so we assume that they are not.
|
|
|
|
prop_assume!(!dims_are_compatible);
|
|
|
|
|
|
|
|
let result = catch_unwind(|| {
|
|
|
|
let mut spmm_result = c.clone();
|
2020-12-21 22:42:32 +08:00
|
|
|
spmm_csr_dense(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref());
|
2020-12-02 23:56:22 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
prop_assert!(result.is_err(),
|
|
|
|
"The SPMM kernel executed successfully despite mismatch dimensions");
|
|
|
|
}
|
|
|
|
|
2020-12-04 21:13:07 +08:00
|
|
|
#[test]
|
2020-12-21 22:09:29 +08:00
|
|
|
fn spadd_pattern_test((a, b) in spadd_pattern_strategy())
|
2020-12-04 21:13:07 +08:00
|
|
|
{
|
2020-12-17 00:30:48 +08:00
|
|
|
// (a, b) are dimensionally compatible patterns
|
|
|
|
let pattern_result = spadd_pattern(&a, &b);
|
2020-12-04 21:13:07 +08:00
|
|
|
|
|
|
|
// To verify the pattern, we construct CSR matrices with positive integer entries
|
|
|
|
// corresponding to a and b, and convert them to dense matrices.
|
|
|
|
// The sum of these dense matrices will then have non-zeros in exactly the same locations
|
|
|
|
// as the result of "adding" the sparsity patterns
|
|
|
|
let a_csr = CsrMatrix::try_from_pattern_and_values(Arc::new(a.clone()), vec![1; a.nnz()])
|
|
|
|
.unwrap();
|
|
|
|
let a_dense = DMatrix::from(&a_csr);
|
|
|
|
let b_csr = CsrMatrix::try_from_pattern_and_values(Arc::new(b.clone()), vec![1; b.nnz()])
|
|
|
|
.unwrap();
|
|
|
|
let b_dense = DMatrix::from(&b_csr);
|
|
|
|
let c_dense = a_dense + b_dense;
|
|
|
|
let c_csr = CsrMatrix::from(&c_dense);
|
|
|
|
|
2020-12-09 21:16:27 +08:00
|
|
|
prop_assert_eq!(&pattern_result, c_csr.pattern().as_ref());
|
2020-12-04 21:13:07 +08:00
|
|
|
}
|
2020-12-10 20:30:37 +08:00
|
|
|
|
|
|
|
#[test]
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spadd_csr_prealloc_test(SpaddCsrArgs { c, beta, alpha, a } in spadd_csr_prealloc_args_strategy()) {
|
2020-12-10 20:30:37 +08:00
|
|
|
// Test that we get the expected result by comparing to an equivalent dense operation
|
|
|
|
// (here we give in the C matrix, so the sparsity pattern is essentially fixed)
|
|
|
|
|
|
|
|
let mut c_sparse = c.clone();
|
2020-12-21 23:05:38 +08:00
|
|
|
spadd_csr_prealloc(beta, &mut c_sparse, alpha, a.as_ref()).unwrap();
|
2020-12-10 20:30:37 +08:00
|
|
|
|
|
|
|
let mut c_dense = DMatrix::from(&c);
|
2020-12-21 22:09:29 +08:00
|
|
|
let op_a_dense = match a {
|
|
|
|
Op::NoOp(a) => DMatrix::from(&a),
|
|
|
|
Op::Transpose(a) => DMatrix::from(&a).transpose(),
|
|
|
|
};
|
2020-12-10 20:30:37 +08:00
|
|
|
c_dense = beta * c_dense + alpha * &op_a_dense;
|
|
|
|
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_sparse), &c_dense);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn csr_add_csr(
|
|
|
|
// a and b have the same dimensions
|
|
|
|
(a, b)
|
|
|
|
in csr_strategy()
|
|
|
|
.prop_flat_map(|a| {
|
2020-12-16 23:17:42 +08:00
|
|
|
let b = csr(PROPTEST_I32_VALUE_STRATEGY, Just(a.nrows()), Just(a.ncols()), 40);
|
2020-12-10 20:30:37 +08:00
|
|
|
(Just(a), b)
|
|
|
|
}))
|
|
|
|
{
|
|
|
|
// We use the dense result as the ground truth for the arithmetic result
|
|
|
|
let c_dense = DMatrix::from(&a) + DMatrix::from(&b);
|
|
|
|
// However, it's not enough only to cover the dense result, we also need to verify the
|
|
|
|
// sparsity pattern. We can determine the exact sparsity pattern by using
|
|
|
|
// dense arithmetic with positive integer values and extracting positive entries.
|
|
|
|
let c_dense_pattern = dense_csr_pattern(a.pattern()) + dense_csr_pattern(b.pattern());
|
|
|
|
let c_pattern = CsrMatrix::from(&c_dense_pattern).pattern().clone();
|
|
|
|
|
|
|
|
// Check each combination of owned matrices and references
|
|
|
|
let c_owned_owned = a.clone() + b.clone();
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_owned_owned), &c_dense);
|
|
|
|
prop_assert_eq!(c_owned_owned.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_owned_ref = a.clone() + &b;
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_owned_ref), &c_dense);
|
|
|
|
prop_assert_eq!(c_owned_ref.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_ref_owned = &a + b.clone();
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_ref_owned), &c_dense);
|
|
|
|
prop_assert_eq!(c_ref_owned.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_ref_ref = &a + &b;
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_ref_ref), &c_dense);
|
|
|
|
prop_assert_eq!(c_ref_ref.pattern(), &c_pattern);
|
|
|
|
}
|
2020-12-14 23:55:06 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn spmm_pattern_test((a, b) in spmm_pattern_strategy())
|
|
|
|
{
|
|
|
|
// (a, b) are multiplication-wise dimensionally compatible patterns
|
|
|
|
let c_pattern = spmm_pattern(&a, &b);
|
|
|
|
|
|
|
|
// To verify the pattern, we construct CSR matrices with positive integer entries
|
|
|
|
// corresponding to a and b, and convert them to dense matrices.
|
|
|
|
// The product of these dense matrices will then have non-zeros in exactly the same locations
|
|
|
|
// as the result of "multiplying" the sparsity patterns
|
|
|
|
let a_csr = CsrMatrix::try_from_pattern_and_values(Arc::new(a.clone()), vec![1; a.nnz()])
|
|
|
|
.unwrap();
|
|
|
|
let a_dense = DMatrix::from(&a_csr);
|
|
|
|
let b_csr = CsrMatrix::try_from_pattern_and_values(Arc::new(b.clone()), vec![1; b.nnz()])
|
|
|
|
.unwrap();
|
|
|
|
let b_dense = DMatrix::from(&b_csr);
|
|
|
|
let c_dense = a_dense * b_dense;
|
|
|
|
let c_csr = CsrMatrix::from(&c_dense);
|
|
|
|
|
|
|
|
prop_assert_eq!(&c_pattern, c_csr.pattern().as_ref());
|
|
|
|
}
|
2020-12-16 21:06:12 +08:00
|
|
|
|
|
|
|
#[test]
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spmm_csr_prealloc_test(SpmmCsrArgs { c, beta, alpha, a, b }
|
|
|
|
in spmm_csr_prealloc_args_strategy()
|
2020-12-16 21:06:12 +08:00
|
|
|
) {
|
|
|
|
// Test that we get the expected result by comparing to an equivalent dense operation
|
|
|
|
// (here we give in the C matrix, so the sparsity pattern is essentially fixed)
|
|
|
|
let mut c_sparse = c.clone();
|
2020-12-21 23:05:38 +08:00
|
|
|
spmm_csr_prealloc(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap();
|
2020-12-16 21:06:12 +08:00
|
|
|
|
|
|
|
let mut c_dense = DMatrix::from(&c);
|
2020-12-21 22:09:29 +08:00
|
|
|
let op_a_dense = match a {
|
|
|
|
Op::NoOp(ref a) => DMatrix::from(a),
|
|
|
|
Op::Transpose(ref a) => DMatrix::from(a).transpose(),
|
|
|
|
};
|
|
|
|
let op_b_dense = match b {
|
|
|
|
Op::NoOp(ref b) => DMatrix::from(b),
|
|
|
|
Op::Transpose(ref b) => DMatrix::from(b).transpose(),
|
|
|
|
};
|
2020-12-16 21:06:12 +08:00
|
|
|
c_dense = beta * c_dense + alpha * &op_a_dense * op_b_dense;
|
|
|
|
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_sparse), &c_dense);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spmm_csr_prealloc_panics_on_dim_mismatch(
|
2020-12-21 22:09:29 +08:00
|
|
|
(alpha, beta, c, a, b)
|
2020-12-16 21:06:12 +08:00
|
|
|
in (PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
csr_strategy(),
|
2020-12-21 22:09:29 +08:00
|
|
|
op_strategy(csr_strategy()),
|
|
|
|
op_strategy(csr_strategy()))
|
2020-12-16 21:06:12 +08:00
|
|
|
) {
|
|
|
|
// We refer to `A * B` as the "product"
|
2020-12-21 22:09:29 +08:00
|
|
|
let product_rows = match &a {
|
|
|
|
Op::NoOp(ref a) => a.nrows(),
|
|
|
|
Op::Transpose(ref a) => a.ncols(),
|
|
|
|
};
|
|
|
|
let product_cols = match &b {
|
|
|
|
Op::NoOp(ref b) => b.ncols(),
|
|
|
|
Op::Transpose(ref b) => b.nrows(),
|
|
|
|
};
|
2020-12-16 21:06:12 +08:00
|
|
|
// Determine the common dimension in the product
|
|
|
|
// from the perspective of a and b, respectively
|
2020-12-21 22:09:29 +08:00
|
|
|
let product_a_common = match &a {
|
|
|
|
Op::NoOp(ref a) => a.ncols(),
|
|
|
|
Op::Transpose(ref a) => a.nrows(),
|
|
|
|
};
|
|
|
|
let product_b_common = match &b {
|
|
|
|
Op::NoOp(ref b) => b.nrows(),
|
|
|
|
Op::Transpose(ref b) => b.ncols(),
|
|
|
|
};
|
2020-12-16 21:06:12 +08:00
|
|
|
|
|
|
|
let dims_are_compatible = product_rows == c.nrows()
|
|
|
|
&& product_cols == c.ncols()
|
|
|
|
&& product_a_common == product_b_common;
|
|
|
|
|
|
|
|
// If the dimensions randomly happen to be compatible, then of course we need to
|
|
|
|
// skip the test, so we assume that they are not.
|
|
|
|
prop_assume!(!dims_are_compatible);
|
|
|
|
|
|
|
|
let result = catch_unwind(|| {
|
|
|
|
let mut spmm_result = c.clone();
|
2020-12-21 23:05:38 +08:00
|
|
|
spmm_csr_prealloc(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap();
|
2020-12-16 21:06:12 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
prop_assert!(result.is_err(),
|
|
|
|
"The SPMM kernel executed successfully despite mismatch dimensions");
|
|
|
|
}
|
2020-12-16 21:21:35 +08:00
|
|
|
|
|
|
|
#[test]
|
2020-12-21 23:05:38 +08:00
|
|
|
fn spadd_csr_prealloc_panics_on_dim_mismatch(
|
2020-12-21 22:09:29 +08:00
|
|
|
(alpha, beta, c, op_a)
|
2020-12-16 21:21:35 +08:00
|
|
|
in (PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
PROPTEST_I32_VALUE_STRATEGY,
|
|
|
|
csr_strategy(),
|
2020-12-21 22:09:29 +08:00
|
|
|
op_strategy(csr_strategy()))
|
2020-12-16 21:21:35 +08:00
|
|
|
) {
|
2020-12-21 22:09:29 +08:00
|
|
|
let op_a_rows = match &op_a {
|
|
|
|
&Op::NoOp(ref a) => a.nrows(),
|
|
|
|
&Op::Transpose(ref a) => a.ncols()
|
|
|
|
};
|
|
|
|
let op_a_cols = match &op_a {
|
|
|
|
&Op::NoOp(ref a) => a.ncols(),
|
|
|
|
&Op::Transpose(ref a) => a.nrows()
|
|
|
|
};
|
2020-12-16 21:21:35 +08:00
|
|
|
|
|
|
|
let dims_are_compatible = c.nrows() == op_a_rows && c.ncols() == op_a_cols;
|
|
|
|
|
|
|
|
// If the dimensions randomly happen to be compatible, then of course we need to
|
|
|
|
// skip the test, so we assume that they are not.
|
|
|
|
prop_assume!(!dims_are_compatible);
|
|
|
|
|
|
|
|
let result = catch_unwind(|| {
|
|
|
|
let mut spmm_result = c.clone();
|
2020-12-21 23:05:38 +08:00
|
|
|
spadd_csr_prealloc(beta, &mut spmm_result, alpha, op_a.as_ref()).unwrap();
|
2020-12-16 21:21:35 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
prop_assert!(result.is_err(),
|
|
|
|
"The SPMM kernel executed successfully despite mismatch dimensions");
|
|
|
|
}
|
2020-12-16 23:17:42 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn csr_mul_csr(
|
|
|
|
// a and b have dimensions compatible for multiplication
|
|
|
|
(a, b)
|
|
|
|
in csr_strategy()
|
|
|
|
.prop_flat_map(|a| {
|
|
|
|
let max_nnz = PROPTEST_MAX_NNZ;
|
|
|
|
let cols = PROPTEST_MATRIX_DIM;
|
|
|
|
let b = csr(PROPTEST_I32_VALUE_STRATEGY, Just(a.ncols()), cols, max_nnz);
|
|
|
|
(Just(a), b)
|
|
|
|
}))
|
|
|
|
{
|
|
|
|
// We use the dense result as the ground truth for the arithmetic result
|
|
|
|
let c_dense = DMatrix::from(&a) * DMatrix::from(&b);
|
|
|
|
// However, it's not enough only to cover the dense result, we also need to verify the
|
|
|
|
// sparsity pattern. We can determine the exact sparsity pattern by using
|
|
|
|
// dense arithmetic with positive integer values and extracting positive entries.
|
|
|
|
let c_dense_pattern = dense_csr_pattern(a.pattern()) * dense_csr_pattern(b.pattern());
|
|
|
|
let c_pattern = CsrMatrix::from(&c_dense_pattern).pattern().clone();
|
|
|
|
|
|
|
|
// Check each combination of owned matrices and references
|
|
|
|
let c_owned_owned = a.clone() * b.clone();
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_owned_owned), &c_dense);
|
|
|
|
prop_assert_eq!(c_owned_owned.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_owned_ref = a.clone() * &b;
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_owned_ref), &c_dense);
|
|
|
|
prop_assert_eq!(c_owned_ref.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_ref_owned = &a * b.clone();
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_ref_owned), &c_dense);
|
|
|
|
prop_assert_eq!(c_ref_owned.pattern(), &c_pattern);
|
|
|
|
|
|
|
|
let c_ref_ref = &a * &b;
|
|
|
|
prop_assert_eq!(&DMatrix::from(&c_ref_ref), &c_dense);
|
|
|
|
prop_assert_eq!(c_ref_ref.pattern(), &c_pattern);
|
|
|
|
}
|
2020-07-14 00:44:40 +08:00
|
|
|
}
|