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}