blob: 1441c4bd72666deea59a458f7bc62257edeb0e97 [file] [log] [blame]
use std::fmt;
use std::vec::*;
use std::string::*;
use std::borrow::*;
use macros::ElementwiseComparator;
use macros::comparison::ComparisonFailure;
const MAX_MISMATCH_REPORTS: usize = 12;
#[doc(hidden)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct VectorElementComparisonFailure<T, E> where E: ComparisonFailure {
pub x: T,
pub y: T,
pub error: E,
pub index: usize
}
impl<T, E> fmt::Display for VectorElementComparisonFailure<T, E>
where T: fmt::Display, E: ComparisonFailure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"#{index}: x = {x}, y = {y}.{reason}",
index = self.index,
x = self.x,
y = self.y,
reason = self.error.failure_reason()
// Add a space before the reason
.map(|s| format!(" {}", s))
.unwrap_or(String::new()))
}
}
#[doc(hidden)]
#[derive(Debug, PartialEq)]
pub enum VectorComparisonResult<T, C, E>
where T: Copy,
C: ElementwiseComparator<T, E>,
E: ComparisonFailure {
Match,
MismatchedDimensions {
dim_x: usize,
dim_y: usize
},
MismatchedElements {
comparator: C,
mismatches: Vec<VectorElementComparisonFailure<T, E>>
}
}
impl <T, C, E> VectorComparisonResult<T, C, E>
where T: Copy + fmt::Display, C: ElementwiseComparator<T, E>, E: ComparisonFailure {
pub fn panic_message(&self) -> Option<String> {
match self {
&VectorComparisonResult::MismatchedElements { ref comparator, ref mismatches } => {
let mut formatted_mismatches = String::new();
let mismatches_overflow = mismatches.len() > MAX_MISMATCH_REPORTS;
let overflow_msg = if mismatches_overflow {
let num_hidden_entries = mismatches.len() - MAX_MISMATCH_REPORTS;
format!(" ... ({} mismatching elements not shown)\n", num_hidden_entries)
} else {
String::new()
};
for mismatch in mismatches.iter().take(MAX_MISMATCH_REPORTS) {
formatted_mismatches.push_str(" ");
formatted_mismatches.push_str(&mismatch.to_string());
formatted_mismatches.push_str("\n");
}
// Strip off the last newline from the above
formatted_mismatches = formatted_mismatches.trim_right().to_string();
Some(format!("\n
Vectors X and Y have {num} mismatched element pairs.
The mismatched elements are listed below, in the format
#index: x = X[index], y = Y[index].
{mismatches}
{overflow_msg}
Comparison criterion: {description}
\n",
num = mismatches.len(),
description = comparator.description(),
mismatches = formatted_mismatches,
overflow_msg = overflow_msg))
},
&VectorComparisonResult::MismatchedDimensions { dim_x, dim_y } => {
Some(format!("\n
Dimensions of vectors X and Y do not match.
dim(X) = {dim_x}
dim(Y) = {dim_y}
\n",
dim_x = dim_x,
dim_y = dim_y))
},
_ => None
}
}
}
#[doc(hidden)]
pub fn elementwise_vector_comparison<T, C, E>(x: &[T], y: &[T], comparator: C)
-> VectorComparisonResult<T, C, E>
where T: Copy,
C: ElementwiseComparator<T, E>,
E: ComparisonFailure {
// The reason this function takes a slice and not a Vector ref,
// is that we the assert_vector_eq! macro to work with both
// references and owned values
if x.len() == y.len() {
let n = x.len();
let mismatches = {
let mut mismatches = Vec::new();
for i in 0 .. n {
let a = x[i].to_owned();
let b = y[i].to_owned();
if let Err(error) = comparator.compare(a, b) {
mismatches.push(VectorElementComparisonFailure {
x: a,
y: b,
error: error,
index: i
});
}
}
mismatches
};
if mismatches.is_empty() {
VectorComparisonResult::Match
} else {
VectorComparisonResult::MismatchedElements {
comparator: comparator,
mismatches: mismatches
}
}
} else {
VectorComparisonResult::MismatchedDimensions { dim_x: x.len(), dim_y: y.len() }
}
}
/// Compare vectors for exact or approximate equality.
///
/// This macro works analogously to [assert_matrix_eq!](macro.assert_matrix_eq.html),
/// but is used for comparing instances of [Vector](vector/struct.Vector.html) rather than
/// matrices. Please see the documentation for `assert_matrix_eq!`
/// for details about issues that arise when comparing floating-point numbers,
/// as well as an explanation for how these macros can be used to resolve
/// these issues.
#[macro_export]
macro_rules! assert_vector_eq {
($x:expr, $y:expr) => {
{
// Note: The reason we take slices of both x and y is that if x or y are passed as references,
// we don't attempt to call elementwise_matrix_comparison with a &&BaseMatrix type (double reference),
// which does not work due to generics.
use $crate::macros::{elementwise_vector_comparison, ExactElementwiseComparator};
let comp = ExactElementwiseComparator;
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
// Note: We need the panic to incur here inside of the macro in order
// for the line number to be correct when using it for tests,
// hence we build the panic message in code, but panic here.
panic!("{msg}
Please see the documentation for ways to compare vectors approximately.\n\n",
msg = msg.trim_right());
}
}
};
($x:expr, $y:expr, comp = exact) => {
{
use $crate::macros::{elementwise_vector_comparison, ExactElementwiseComparator};
let comp = ExactElementwiseComparator;
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
panic!(msg);
}
}
};
($x:expr, $y:expr, comp = abs, tol = $tol:expr) => {
{
use $crate::macros::{elementwise_vector_comparison, AbsoluteElementwiseComparator};
let comp = AbsoluteElementwiseComparator { tol: $tol };
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
panic!(msg);
}
}
};
($x:expr, $y:expr, comp = ulp, tol = $tol:expr) => {
{
use $crate::macros::{elementwise_vector_comparison, UlpElementwiseComparator};
let comp = UlpElementwiseComparator { tol: $tol };
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
panic!(msg);
}
}
};
($x:expr, $y:expr, comp = float) => {
{
use $crate::macros::{elementwise_vector_comparison, FloatElementwiseComparator};
let comp = FloatElementwiseComparator::default();
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
panic!(msg);
}
}
};
// This following allows us to optionally tweak the epsilon and ulp tolerances
// used in the default float comparator.
($x:expr, $y:expr, comp = float, $($key:ident = $val:expr),+) => {
{
use $crate::macros::{elementwise_vector_comparison, FloatElementwiseComparator};
let comp = FloatElementwiseComparator::default()$(.$key($val))+;
let msg = elementwise_vector_comparison($x.data(), $y.data(), comp).panic_message();
if let Some(msg) = msg {
panic!(msg);
}
}
};
}
#[cfg(test)]
mod tests {
use super::{
elementwise_vector_comparison,
VectorComparisonResult};
use macros::comparison::{
ExactElementwiseComparator, ExactError
};
use quickcheck::TestResult;
quickcheck! {
fn property_elementwise_vector_comparison_incompatible_vectors_yields_dimension_mismatch(
m: usize,
n: usize) -> TestResult {
if m == n {
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 = vector![0; m];
let ref y = vector![0; n];
let expected = VectorComparisonResult::MismatchedDimensions { dim_x: m, dim_y: n };
TestResult::from_bool(elementwise_vector_comparison(x.data(), y.data(), comp) == expected)
}
}
quickcheck! {
fn property_elementwise_vector_comparison_vector_matches_self(m: usize) -> bool {
let comp = ExactElementwiseComparator;
let ref x = vector![0; m];
elementwise_vector_comparison(x.data(), x.data(), comp) == VectorComparisonResult::Match
}
}
#[test]
fn elementwise_vector_comparison_reports_correct_mismatches() {
use super::VectorComparisonResult::MismatchedElements;
use super::VectorElementComparisonFailure;
let comp = ExactElementwiseComparator;
{
// Single element vectors
let x = vector![1];
let y = vector![2];
let expected = MismatchedElements {
comparator: comp,
mismatches: vec![VectorElementComparisonFailure {
x: 1, y: 2,
error: ExactError,
index: 0
}]
};
assert_eq!(elementwise_vector_comparison(x.data(), y.data(), comp), expected);
}
{
// Mismatch for first and last elements of a vector
let x = vector![0, 1, 2];
let y = vector![1, 1, 3];
let mismatches = vec![
VectorElementComparisonFailure {
x: 0, y: 1,
error: ExactError,
index: 0
},
VectorElementComparisonFailure {
x: 2, y: 3,
error: ExactError,
index: 2
}
];
let expected = MismatchedElements {
comparator: comp,
mismatches: mismatches
};
assert_eq!(elementwise_vector_comparison(x.data(), y.data(), comp), expected);
}
{
// Check some arbitrary elements
let x = vector![0, 1, 2, 3, 4, 5];
let y = vector![0, 2, 2, 3, 5, 5];
let mismatches = vec![
VectorElementComparisonFailure {
x: 1, y: 2,
error: ExactError,
index: 1
},
VectorElementComparisonFailure {
x: 4, y: 5,
error: ExactError,
index: 4
}
];
let expected = MismatchedElements {
comparator: comp,
mismatches: mismatches
};
assert_eq!(elementwise_vector_comparison(x.data(), y.data(), comp), expected);
}
}
#[test]
pub fn vector_eq_default_compare_self_for_integer() {
let x = vector![1, 2, 3 , 4];
assert_vector_eq!(x, x);
}
#[test]
pub fn vector_eq_default_compare_self_for_floating_point() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x);
}
#[test]
#[should_panic]
pub fn vector_eq_default_mismatched_elements() {
let x = vector![1, 2, 3, 4];
let y = vector![1, 2, 4, 4];
assert_vector_eq!(x, y);
}
#[test]
#[should_panic]
pub fn vector_eq_default_mismatched_dimensions() {
let x = vector![1, 2, 3, 4];
let y = vector![1, 2, 3];
assert_vector_eq!(x, y);
}
#[test]
pub fn vector_eq_exact_compare_self_for_integer() {
let x = vector![1, 2, 3, 4];
assert_vector_eq!(x, x, comp = exact);
}
#[test]
pub fn vector_eq_exact_compare_self_for_floating_point() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = exact);;
}
#[test]
#[should_panic]
pub fn vector_eq_exact_mismatched_elements() {
let x = vector![1, 2, 3, 4];
let y = vector![1, 2, 4, 4];
assert_vector_eq!(x, y, comp = exact);
}
#[test]
#[should_panic]
pub fn vector_eq_exact_mismatched_dimensions() {
let x = vector![1, 2, 3, 4];
let y = vector![1, 2, 3];
assert_vector_eq!(x, y, comp = exact);
}
#[test]
pub fn vector_eq_abs_compare_self_for_integer() {
let x = vector![1, 2, 3, 4];
assert_vector_eq!(x, x, comp = abs, tol = 1);
}
#[test]
pub fn vector_eq_abs_compare_self_for_floating_point() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = abs, tol = 1e-8);
}
#[test]
#[should_panic]
pub fn vector_eq_abs_mismatched_elements() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0, 4.0];
assert_vector_eq!(x, y, comp = abs, tol = 1e-8);
}
#[test]
#[should_panic]
pub fn vector_eq_abs_mismatched_dimensions() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0];
assert_vector_eq!(x, y, comp = abs, tol = 1e-8);
}
#[test]
pub fn vector_eq_ulp_compare_self() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = ulp, tol = 1);
}
#[test]
#[should_panic]
pub fn vector_eq_ulp_mismatched_elements() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0, 4.0];
assert_vector_eq!(x, y, comp = ulp, tol = 4);
}
#[test]
#[should_panic]
pub fn vector_eq_ulp_mismatched_dimensions() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0];
assert_vector_eq!(x, y, comp = ulp, tol = 4);
}
#[test]
pub fn vector_eq_float_compare_self() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = ulp, tol = 1);
}
#[test]
#[should_panic]
pub fn vector_eq_float_mismatched_elements() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0, 4.0];
assert_vector_eq!(x, y, comp = float);
}
#[test]
#[should_panic]
pub fn vector_eq_float_mismatched_dimensions() {
let x = vector![1.0, 2.0, 3.0, 4.0];
let y = vector![1.0, 2.0, 4.0];
assert_vector_eq!(x, y, comp = float);
}
#[test]
pub fn vector_eq_float_compare_self_with_eps() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = float, eps = 1e-6);
}
#[test]
pub fn vector_eq_float_compare_self_with_ulp() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = float, ulp = 12);
}
#[test]
pub fn vector_eq_float_compare_self_with_eps_and_ulp() {
let x = vector![1.0, 2.0, 3.0, 4.0];
assert_vector_eq!(x, x, comp = float, eps = 1e-6, ulp = 12);
assert_vector_eq!(x, x, comp = float, ulp = 12, eps = 1e-6);
}
#[test]
pub fn vector_eq_pass_by_ref()
{
let x = vector![0.0];
// Exercise all the macro definitions and make sure that we are able to call it
// when the arguments are references.
assert_vector_eq!(&x, &x);
assert_vector_eq!(&x, &x, comp = exact);
assert_vector_eq!(&x, &x, comp = abs, tol = 0.0);
assert_vector_eq!(&x, &x, comp = ulp, tol = 0);
assert_vector_eq!(&x, &x, comp = float);
assert_vector_eq!(&x, &x, comp = float, eps = 0.0, ulp = 0);
}
}