matrixcompare/
macros.rs

1/// Internal macro used for providing consistent macro arguments across several macros
2#[doc(hidden)]
3#[macro_export]
4macro_rules! base_matrix_eq {
5    ($failure_handler:expr, $x:expr, $y:expr) => {
6        {
7            use $crate::{compare_matrices};
8            use $crate::comparators::ExactElementwiseComparator;
9
10            let comp = ExactElementwiseComparator;
11            let result = compare_matrices(&$x, &$y, &comp);
12            if let Err(failure) = result {
13                // Note: We need the panic to incur here inside of the macro in order
14                // for the line number to be correct when using it for tests,
15                // hence we build the panic message in code, but panic here.
16                let message = format!("{}\n
17Please see the documentation for ways to compare matrices approximately.\n",
18                    failure);
19                return $failure_handler(message);
20            }
21        }
22    };
23    ($failure_handler:expr, $x:expr, $y:expr, comp = exact) => {
24        {
25            use $crate::{compare_matrices};
26            use $crate::comparators::ExactElementwiseComparator;
27
28            let comp = ExactElementwiseComparator;
29            let result = compare_matrices(&$x, &$y, &comp);
30            if let Err(failure) = result {
31                let message = format!("{}\n", failure);
32                return $failure_handler(message);
33            }
34        }
35    };
36    ($failure_handler:expr, $x:expr, $y:expr, comp = abs, tol = $tol:expr) => {
37        {
38            use $crate::{compare_matrices};
39            use $crate::comparators::AbsoluteElementwiseComparator;
40
41            let comp = AbsoluteElementwiseComparator { tol: $tol };
42            let result = compare_matrices(&$x, &$y, &comp);
43            if let Err(failure) = result {
44                let message = format!("{}\n", failure);
45                return $failure_handler(message);
46            }
47        }
48    };
49    ($failure_handler:expr, $x:expr, $y:expr, comp = ulp, tol = $tol:expr) => {
50        {
51            use $crate::{compare_matrices};
52            use $crate::comparators::UlpElementwiseComparator;
53
54            let comp = UlpElementwiseComparator { tol: $tol };
55            let result = compare_matrices(&$x, &$y, &comp);
56            if let Err(failure) = result {
57                let message = format!("{}\n", failure);
58                return $failure_handler(message);
59            }
60        }
61    };
62    ($failure_handler:expr, $x:expr, $y:expr, comp = float) => {
63        {
64            use $crate::{compare_matrices};
65            use $crate::comparators::FloatElementwiseComparator;
66
67            let comp = FloatElementwiseComparator::default();
68            let result = compare_matrices(&$x, &$y, &comp);
69            if let Err(failure) = result {
70                let message = format!("{}", failure);
71                return $failure_handler(message);
72            }
73        }
74    };
75    // This following allows us to optionally tweak the epsilon and ulp tolerances
76    // used in the default float comparator.
77    ($failure_handler:expr, $x:expr, $y:expr, comp = float, $($key:ident = $val:expr),+) => {
78        {
79            use $crate::{compare_matrices};
80            use $crate::comparators::FloatElementwiseComparator;
81
82            let comp = FloatElementwiseComparator::default()$(.$key($val))+;
83            let result = compare_matrices(&$x, &$y, &comp);
84            if let Err(failure) = result {
85                let message = format!("{}", failure);
86                return $failure_handler(message);
87            }
88        }
89    };
90}
91
92/// Compare matrices for exact or approximate equality.
93///
94/// The `assert_matrix_eq!` simplifies the comparison of two matrices by
95/// providing the following features:
96///
97/// - Verifies that the dimensions of the matrices match.
98/// - Offers both exact and approximate comparison of individual elements.
99/// - Multiple types of comparators available, depending on the needs of the user.
100/// - Built-in error reporting makes it easy to determine which elements of the two matrices
101///   that do not compare equal.
102///
103/// # Usage
104/// Given two matrices `x` and `y`, the default invocation performs an exact elementwise
105/// comparison of the two matrices.
106///
107/// ```
108/// # use matrixcompare::assert_matrix_eq; use matrixcompare_mock::mock_matrix;
109/// # let x = mock_matrix![1.0f64]; let y = mock_matrix![1.0f64];
110/// // Performs elementwise exact comparison
111/// assert_matrix_eq!(x, y);
112/// ```
113///
114/// An exact comparison is often not desirable. In particular, with floating point types,
115/// rounding errors or other sources of inaccuracies tend to complicate the matter.
116/// For this purpose, `assert_matrix_eq!` provides several comparators.
117///
118/// ```
119/// # use matrixcompare::assert_matrix_eq; use matrixcompare_mock::mock_matrix;
120/// # let x = mock_matrix![1.0f64]; let y = mock_matrix![1.0f64];
121/// // Available comparators:
122/// assert_matrix_eq!(x, y, comp = exact);
123/// assert_matrix_eq!(x, y, comp = float);
124/// assert_matrix_eq!(x, y, comp = abs, tol = 1e-12);
125/// assert_matrix_eq!(x, y, comp = ulp, tol = 8);
126/// ```
127/// **Note**: The `comp` argument *must* be specified after `x` and `y`, and cannot come
128/// after comparator-specific options. This is a deliberate design decision,
129/// with the rationale that assertions should look as uniform as possible for
130/// the sake of readability.
131///
132///
133/// ### The `exact` comparator
134/// This comparator simply uses the default `==` operator to compare each pair of elements.
135/// The default comparator delegates the comparison to the `exact` comparator.
136///
137/// ### The `float` comparator
138/// The `float` comparator is designed to be a conservative default for comparing floating-point numbers.
139/// It is inspired by the `AlmostEqualUlpsAndAbs` comparison function proposed in the excellent blog post
140/// [Comparing Floating Point Numbers, 2012 Edition](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
141/// by Bruce Dawson.
142///
143/// If you expect the two matrices to be almost exactly the same, but you want to leave some
144/// room for (very small) rounding errors, then this comparator should be your default choice.
145///
146/// The comparison criterion can be summarized as follows:
147///
148/// 1. If `assert_matrix_eq!(x, y, comp = abs, tol = max_eps)` holds for `max_eps` close to the
149///    machine epsilon for the floating point type,
150///    then the comparison is successful.
151/// 2. Otherwise, returns the result of `assert_matrix_eq!(x, y, comp = ulp, tol = max_ulp)`,
152///    where `max_ulp` is a small positive integer constant.
153///
154/// The `max_eps` and `max_ulp` parameters can be tweaked to your preference with the syntax:
155///
156/// ```
157/// # use matrixcompare::assert_matrix_eq; use matrixcompare_mock::mock_matrix;
158/// # let x = mock_matrix![1.0f64]; let y = mock_matrix![1.0f64];
159/// # let max_eps = 1.0; let max_ulp = 0;
160/// assert_matrix_eq!(x, y, comp = float, eps = max_eps, ulp = max_ulp);
161/// ```
162///
163/// These additional parameters can be specified in any order after the choice of comparator,
164/// and do not both need to be present.
165///
166/// ### The `abs` comparator
167/// Compares the absolute difference between individual elements against the specified tolerance.
168/// Specifically, for every pair of elements x and y picked from the same row and column in X and Y
169/// respectively, the criterion is defined by
170///
171/// ```text
172///     | x - y | <= tol.
173/// ```
174///
175/// In addition to floating point numbers, the comparator can also be used for integral numbers,
176/// both signed and unsigned. In order to avoid unsigned underflow, the difference is always
177/// computed by subtracting the smaller number from the larger number.
178/// Note that the type of `tol` is required to be the same as that of the scalar field.
179///
180///
181/// ### The `ulp` comparator
182/// Elementwise comparison of floating point numbers based on their
183/// [ULP](https://en.wikipedia.org/wiki/Unit_in_the_last_place) difference.
184/// Once again, this is inspired by the proposals
185/// [in the aforementioned blog post by Bruce Dawson](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition),
186/// but it handles some cases explicitly as to provide better error reporting.
187///
188/// Note that the ULP difference of two floating point numbers is not defined in the following cases:
189///
190/// - The two numbers have different signs. The only exception here is +0 and -0,
191///   which are considered an exact match.
192/// - One of the numbers is NaN.
193///
194/// ULP-based comparison is typically used when two numbers are expected to be very,
195/// very close to each other. However, it is typically not very useful very close to zero,
196/// which is discussed in the linked blog post above.
197/// The error in many mathematical functions can often be bounded by a certain number of ULP, and so
198/// this comparator is particularly useful if this number is known.
199///
200/// Note that the scalar type of the matrix must implement the [Ulp trait](crate::ulp::Ulp) in order
201/// to be used with this comparator. By default, `f32` and `f64` implementations are provided.
202///
203/// # Error reporting
204///
205/// One of the main motivations for the `assert_matrix_eq!` macro is the ability to give
206/// useful error messages which help pinpoint the problems. For example, consider the example
207///
208/// ```rust,should_panic
209/// # use matrixcompare::assert_matrix_eq; use matrixcompare_mock::mock_matrix;
210///
211/// fn main() {
212///     let a = mock_matrix![1.00, 2.00;
213///                          3.00, 4.00];
214///     let b = mock_matrix![1.01, 2.00;
215///                          3.40, 4.00];
216///     assert_matrix_eq!(a, b, comp = abs, tol = 1e-8);
217/// }
218/// ```
219///
220/// which yields the output
221///
222/// ```text
223/// Matrices X and Y have 2 mismatched element pairs.
224/// The mismatched elements are listed below, in the format
225/// (row, col): x = X[[row, col]], y = Y[[row, col]].
226///
227/// (0, 0): x = 1, y = 1.01. Absolute error: 0.010000000000000009.
228/// (1, 0): x = 3, y = 3.4. Absolute error: 0.3999999999999999.
229///
230/// Comparison criterion: absolute difference, |x - y| <= 0.00000001.
231/// ```
232///
233/// # Trait bounds on elements
234/// Each comparator has specific requirements on which traits the elements
235/// need to implement. To discover which traits are required for each comparator,
236/// we refer the reader to implementors of
237/// [ElementwiseComparator](crate::comparators::ElementwiseComparator),
238/// which provides the underlying comparison for the various macro invocations.
239///
240/// # Examples
241///
242/// ```
243/// # use matrixcompare::assert_matrix_eq; use matrixcompare_mock::mock_matrix;
244///
245/// let ref a = mock_matrix![1, 2;
246///                          3, 4i64];
247/// let ref b = mock_matrix![1, 3;
248///                          3, 4i64];
249///
250/// let ref x = mock_matrix![1.000, 2.000,
251///                          3.000, 4.000f64];
252/// let ref y = mock_matrix![0.999, 2.001,
253///                          2.998, 4.000f64];
254///
255/// // comp = abs is also applicable to integers
256/// assert_matrix_eq!(a, b, comp = abs, tol = 1);
257/// assert_matrix_eq!(x, y, comp = abs, tol = 0.01);
258/// ```
259#[macro_export]
260macro_rules! assert_matrix_eq {
261    ($($args:tt)*) => {
262        $crate::base_matrix_eq!(|msg| panic!("{}", msg), $($args)*);
263    };
264}
265
266/// Internal macro used for providing consistent macro arguments across several scalar comparison
267/// macros.
268#[doc(hidden)]
269#[macro_export]
270macro_rules! base_scalar_eq {
271    ($failure_handler:expr, $x:expr, $y:expr) => {
272        {
273            use $crate::{compare_scalars};
274            use $crate::comparators::ExactElementwiseComparator;
275            use std::borrow::Borrow;
276            let comp = ExactElementwiseComparator;
277            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
278            if let Err(error) = result {
279                let message = format!("{}\n
280Please see the documentation for ways to compare scalars approximately.\n",
281                    error);
282                return $failure_handler(message);
283            }
284        }
285    };
286    ($failure_handler:expr, $x:expr, $y:expr, comp = exact) => {
287        {
288            use $crate::{compare_scalars};
289            use $crate::comparators::ExactElementwiseComparator;
290            use std::borrow::Borrow;
291            let comp = ExactElementwiseComparator;
292            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
293            if let Err(error) = result {
294                let message = format!("{}\n", error);
295                return $failure_handler(message);
296            }
297        }
298    };
299    ($failure_handler:expr, $x:expr, $y:expr, comp = abs, tol = $tol:expr) => {
300        {
301            use $crate::{compare_scalars};
302            use $crate::comparators::AbsoluteElementwiseComparator;
303            use std::borrow::Borrow;
304            let comp = AbsoluteElementwiseComparator { tol: $tol.clone() };
305            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
306            if let Err(error) = result {
307                let message = format!("{}\n", error);
308                return $failure_handler(message);
309            }
310        }
311    };
312    ($failure_handler:expr, $x:expr, $y:expr, comp = ulp, tol = $tol:expr) => {
313        {
314            use $crate::{compare_scalars};
315            use $crate::comparators::UlpElementwiseComparator;
316            use std::borrow::Borrow;
317            let comp = UlpElementwiseComparator { tol: $tol.clone() };
318            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
319            if let Err(error) = result {
320                let message = format!("{}\n", error);
321                return $failure_handler(message);
322            }
323        }
324    };
325    ($failure_handler:expr, $x:expr, $y:expr, comp = float) => {
326        {
327            use $crate::{compare_scalars};
328            use $crate::comparators::FloatElementwiseComparator;
329            use std::borrow::Borrow;
330            let comp = FloatElementwiseComparator::default();
331            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
332            if let Err(error) = result {
333                let message = format!("{}\n", error);
334                return $failure_handler(message);
335            }
336        }
337    };
338    // The following allows us to optionally tweak the epsilon and ulp tolerances
339    // used in the default float comparator.
340    ($failure_handler:expr, $x:expr, $y:expr, comp = float, $($key:ident = $val:expr),+) => {
341        {
342            use $crate::{compare_scalars};
343            use $crate::comparators::FloatElementwiseComparator;
344            use std::borrow::Borrow;
345            let comp = FloatElementwiseComparator::default()$(.$key($val))+;
346            let result = compare_scalars($x.borrow(), $y.borrow(), comp);
347            if let Err(error) = result {
348                let message = format!("{}\n", error);
349                return $failure_handler(message);
350            }
351        }
352    };
353}
354
355/// Compare scalars for exact or approximate equality.
356///
357/// This macro works analogously to [assert_matrix_eq!],
358/// but is used for comparing scalars (e.g. integers, floating-point numbers)
359/// rather than matrices. Please see the documentation for `assert_matrix_eq!`
360/// for details about issues that arise when comparing floating-point numbers,
361/// as well as an explanation for how these macros can be used to resolve
362/// these issues.
363///
364/// # Examples
365///
366/// ```
367/// # use matrixcompare::{assert_scalar_eq};
368/// let x = 3.00;
369/// let y = 3.05;
370/// // Assert that |x - y| <= 0.1
371/// assert_scalar_eq!(x, y, comp = abs, tol = 0.1);
372/// ```
373#[macro_export]
374macro_rules! assert_scalar_eq {
375    ($($args:tt)*) => {
376        $crate::base_scalar_eq!(|msg| panic!("{}", msg), $($args)*);
377    };
378}