matrixcompare 0.3.0

Tools for convenient comparison of matrices
Documentation
use matrixcompare::comparators::{ElementwiseComparator, ExactElementwiseComparator, ExactError};
use matrixcompare::{assert_matrix_eq, ElementsMismatch};
use matrixcompare::{compare_matrices, DimensionMismatch, MatrixComparisonFailure};
use matrixcompare_mock::{dense_matrix_strategy_i64, mock_matrix, MockDenseMatrix};
use quickcheck::{quickcheck, TestResult};

use proptest::prelude::*;

mod common;
use common::{reverse_result, MATRIX_DIM_RANGE};

quickcheck! {
    fn property_elementwise_comparison_incompatible_matrices_yield_dimension_mismatch(
        m: usize,
        n: usize,
        p: usize,
        q: usize) -> TestResult {
        if m == p && n == q {
            return TestResult::discard()
        }

        // It does not actually matter which comparator we use here, but we need to pick one
        let comp = ExactElementwiseComparator;
        let ref x = MockDenseMatrix::from_row_major(m, n, vec![0; m * n]);
        let ref y = MockDenseMatrix::from_row_major(p, q, vec![0; p * q]);

        let expected = MatrixComparisonFailure::MismatchedDimensions(
            DimensionMismatch { dim_left: (m, n), dim_right: (p, q) }
        );

        TestResult::from_bool(compare_matrices(x, y, &comp) == Err(expected))
    }
}

quickcheck! {
    fn property_elementwise_comparison_matrix_matches_self(m: usize, n: usize) -> bool {
        let comp = ExactElementwiseComparator;
        let ref x = MockDenseMatrix::from_row_major(m, n, vec![0; m * n]);

        compare_matrices(x, x, &comp).is_ok()
    }
}

#[test]
fn compare_matrices_reports_correct_mismatches() {
    use matrixcompare::MatrixComparisonFailure::MismatchedElements;
    use matrixcompare::MatrixElementComparisonFailure;

    let comp = ExactElementwiseComparator;
    let description =
        <ExactElementwiseComparator as ElementwiseComparator<usize>>::description(&comp);

    {
        // Single element matrices
        let ref x = MockDenseMatrix::from_row_major(1, 1, vec![1]);
        let ref y = MockDenseMatrix::from_row_major(1, 1, vec![2]);

        let expected = MismatchedElements(ElementsMismatch {
            comparator_description: description.clone(),
            mismatches: vec![MatrixElementComparisonFailure {
                left: 1,
                right: 2,
                error: ExactError,
                row: 0,
                col: 0,
            }],
        });

        assert_eq!(compare_matrices(x, y, &comp), Err(expected));
    }

    {
        // Mismatch in top-left and bottom-corner elements for a short matrix
        let ref x = MockDenseMatrix::from_row_major(2, 3, vec![0, 1, 2, 3, 4, 5]);
        let ref y = MockDenseMatrix::from_row_major(2, 3, vec![1, 1, 2, 3, 4, 6]);
        let mismatches = vec![
            MatrixElementComparisonFailure {
                left: 0,
                right: 1,
                error: ExactError,
                row: 0,
                col: 0,
            },
            MatrixElementComparisonFailure {
                left: 5,
                right: 6,
                error: ExactError,
                row: 1,
                col: 2,
            },
        ];

        let expected = MismatchedElements(ElementsMismatch {
            comparator_description: description.clone(),
            mismatches: mismatches,
        });

        assert_eq!(compare_matrices(x, y, &comp), Err(expected));
    }

    {
        // Mismatch in top-left and bottom-corner elements for a tall matrix
        let ref x = mock_matrix![ 0, 1;
                                  2, 3;
                                  4, 5 ];
        let ref y = mock_matrix![ 1, 1;
                                  2, 3;
                                  4, 6 ];
        let mismatches = vec![
            MatrixElementComparisonFailure {
                left: 0,
                right: 1,
                error: ExactError,
                row: 0,
                col: 0,
            },
            MatrixElementComparisonFailure {
                left: 5,
                right: 6,
                error: ExactError,
                row: 2,
                col: 1,
            },
        ];

        let expected = MismatchedElements(ElementsMismatch {
            comparator_description: description.clone(),
            mismatches: mismatches,
        });

        assert_eq!(compare_matrices(x, y, &comp), Err(expected));
    }

    {
        // Check some arbitrary elements
        let ref x = MockDenseMatrix::from_row_major(2, 4, vec![0, 1, 2, 3, 4, 5, 6, 7]);
        let ref y = MockDenseMatrix::from_row_major(2, 4, vec![0, 1, 3, 3, 4, 6, 6, 7]);

        let mismatches = vec![
            MatrixElementComparisonFailure {
                left: 2,
                right: 3,
                error: ExactError,
                row: 0,
                col: 2,
            },
            MatrixElementComparisonFailure {
                left: 5,
                right: 6,
                error: ExactError,
                row: 1,
                col: 1,
            },
        ];

        let expected = MismatchedElements(ElementsMismatch {
            comparator_description: description.clone(),
            mismatches: mismatches,
        });

        assert_eq!(compare_matrices(x, y, &comp), Err(expected));
    }
}

#[test]
pub fn matrix_eq_absolute_compare_self_for_integer() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1, 2, 3, 4, 5, 6]);
    assert_matrix_eq!(x, x, comp = abs, tol = 0);
}

#[test]
pub fn matrix_eq_absolute_compare_self_for_floating_point() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = abs, tol = 0.0);
}

#[test]
#[should_panic]
pub fn matrix_eq_absolute_mismatched_dimensions() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1, 2, 3, 4, 5, 6]);
    let y = MockDenseMatrix::from_row_major(2, 3, vec![1, 2, 3, 4]);
    assert_matrix_eq!(x, y, comp = abs, tol = 0);
}

#[test]
#[should_panic]
pub fn matrix_eq_absolute_mismatched_floating_point_elements() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.00, 2.00, 3.00, 4.00, 5.00, 6.00]);
    let y = MockDenseMatrix::from_row_major(2, 3, vec![1.00, 2.01, 3.00, 3.99, 5.00, 6.00]);
    assert_matrix_eq!(x, y, comp = abs, tol = 1e-10);
}

#[test]
pub fn matrix_eq_exact_compare_self_for_integer() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1, 2, 3, 4, 5, 6]);
    assert_matrix_eq!(x, x, comp = exact);
}

#[test]
pub fn matrix_eq_exact_compare_self_for_floating_point() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = exact);
}

#[test]
pub fn matrix_eq_ulp_compare_self() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = ulp, tol = 0);
}

#[test]
pub fn matrix_eq_default_compare_self_for_floating_point() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x);
}

#[test]
pub fn matrix_eq_default_compare_self_for_integer() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1, 2, 3, 4, 5, 6]);
    assert_matrix_eq!(x, x);
}

#[test]
#[should_panic]
pub fn matrix_eq_ulp_different_signs() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    let y = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, -3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, y, comp = ulp, tol = 0);
}

#[test]
#[should_panic]
pub fn matrix_eq_ulp_nan() {
    use std::f64;
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    let y = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, f64::NAN, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, y, comp = ulp, tol = 0);
}

#[test]
pub fn matrix_eq_float_compare_self() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = float);
}

#[test]
pub fn matrix_eq_float_compare_self_with_eps() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = float, eps = 1e-6);
}

#[test]
pub fn matrix_eq_float_compare_self_with_ulp() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = float, ulp = 12);
}

#[test]
pub fn matrix_eq_float_compare_self_with_eps_and_ulp() {
    let x = MockDenseMatrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
    assert_matrix_eq!(x, x, comp = float, eps = 1e-6, ulp = 12);
    assert_matrix_eq!(x, x, comp = float, ulp = 12, eps = 1e-6);
}

#[test]
pub fn matrix_eq_pass_by_ref() {
    let x = MockDenseMatrix::from_row_major(1, 1, vec![0.0f64]);

    // Exercise all the macro definitions and make sure that we are able to call it
    // when the arguments are references.
    assert_matrix_eq!(&x, &x);
    assert_matrix_eq!(&x, &x, comp = exact);
    assert_matrix_eq!(&x, &x, comp = abs, tol = 0.0);
    assert_matrix_eq!(&x, &x, comp = ulp, tol = 0);
    assert_matrix_eq!(&x, &x, comp = float);
    assert_matrix_eq!(&x, &x, comp = float, eps = 0.0, ulp = 0);
}

proptest! {
    #[test]
    fn dense_dense_comparison_is_symmetric_for_compatible_matrices_i64(
        // Generate two dense matrices which have the same dimensions
        (dense1, dense2) in (MATRIX_DIM_RANGE, MATRIX_DIM_RANGE).prop_flat_map(|(r, c)| {
            (dense_matrix_strategy_i64(r..=r, c..=c), dense_matrix_strategy_i64(r..=r, c..=c))
        })
    ) {
        let c = ExactElementwiseComparator;
        let result1 = compare_matrices(&dense1, &dense2, &c);
        let result2 = compare_matrices(&dense2, &dense1, &c);

        // TODO: Create issue in proptest repo for the fact that prop_assert_eq! moves objects,
        // whereas assert_eq! does not
        prop_assert_eq!(result1.clone(), reverse_result(result2.clone()));
        prop_assert_eq!(reverse_result(result1), result2);
    }

    #[test]
    fn dense_dense_comparison_is_symmetric_for_all_matrices_i64(
        // Generate two dense matrices which have the same dimensions
        dense1 in dense_matrix_strategy_i64(MATRIX_DIM_RANGE, MATRIX_DIM_RANGE),
        dense2 in dense_matrix_strategy_i64(MATRIX_DIM_RANGE, MATRIX_DIM_RANGE)
    ) {
        let c = ExactElementwiseComparator;
        let result1 = compare_matrices(&dense1, &dense2, &c);
        let result2 = compare_matrices(&dense2, &dense1, &c);
        prop_assert_eq!(result1.clone(), reverse_result(result2.clone()));
        prop_assert_eq!(reverse_result(result1), result2);
    }
}