|
1 |
| -//! Assertions for value and array |
| 1 | +//! Assertions for array |
2 | 2 |
|
3 |
| -use ndarray::{Array, Dimension, IntoDimension}; |
4 |
| -use float_cmp::ApproxEqRatio; |
5 |
| -use num_complex::Complex; |
| 3 | +use std::iter::Sum; |
| 4 | +use num_traits::Float; |
| 5 | +use ndarray::*; |
6 | 6 |
|
7 |
| -/// test two values are close in relative tolerance sense |
8 |
| -pub trait AssertClose: Sized + Copy { |
9 |
| - type Tol; |
10 |
| - fn assert_close(self, truth: Self, rtol: Self::Tol); |
| 7 | +use super::vector::*; |
| 8 | + |
| 9 | +pub trait Close: Absolute { |
| 10 | + fn rclose(self, truth: Self, relative_tol: Self::Output) -> Result<Self::Output, Self::Output>; |
| 11 | + fn aclose(self, truth: Self, absolute_tol: Self::Output) -> Result<Self::Output, Self::Output>; |
11 | 12 | }
|
12 | 13 |
|
13 | 14 | macro_rules! impl_AssertClose {
|
14 | 15 | ($scalar:ty) => {
|
15 |
| -impl AssertClose for $scalar { |
16 |
| - type Tol = $scalar; |
17 |
| - fn assert_close(self, truth: Self, rtol: Self::Tol) { |
18 |
| - if !self.approx_eq_ratio(&truth, rtol) { |
19 |
| - panic!("Not close: val={}, truth={}, rtol={}", self, truth, rtol); |
| 16 | +impl Close for $scalar { |
| 17 | + fn rclose(self, truth: Self, rtol: Self::Output) -> Result<Self::Output, Self::Output> { |
| 18 | + let dev = (self - truth).abs() / truth.abs(); |
| 19 | + if dev < rtol { |
| 20 | + Ok(dev) |
| 21 | + } else { |
| 22 | + Err(dev) |
20 | 23 | }
|
21 | 24 | }
|
22 |
| -} |
23 |
| -impl AssertClose for Complex<$scalar> { |
24 |
| - type Tol = $scalar; |
25 |
| - fn assert_close(self, truth: Self, rtol: Self::Tol) { |
26 |
| - if !(self.re.approx_eq_ratio(&truth.re, rtol) && self.im.approx_eq_ratio(&truth.im, rtol)) { |
27 |
| - panic!("Not close: val={}, truth={}, rtol={}", self, truth, rtol); |
| 25 | + |
| 26 | + fn aclose(self, truth: Self, atol: Self::Output) -> Result<Self::Output, Self::Output> { |
| 27 | + let dev = (self - truth).abs(); |
| 28 | + if dev < atol { |
| 29 | + Ok(dev) |
| 30 | + } else { |
| 31 | + Err(dev) |
28 | 32 | }
|
29 | 33 | }
|
30 | 34 | }
|
31 | 35 | }} // impl_AssertClose
|
32 | 36 | impl_AssertClose!(f64);
|
33 | 37 | impl_AssertClose!(f32);
|
34 | 38 |
|
35 |
| -/// test two arrays are close |
36 |
| -pub trait AssertAllClose { |
37 |
| - type Tol; |
38 |
| - /// test two arrays are close in L2-norm with relative tolerance |
39 |
| - fn assert_allclose_l2(&self, truth: &Self, rtol: Self::Tol); |
40 |
| - /// test two arrays are close in inf-norm with absolute tolerance |
41 |
| - fn assert_allclose_inf(&self, truth: &Self, atol: Self::Tol); |
| 39 | +#[macro_export] |
| 40 | +macro_rules! assert_rclose { |
| 41 | + ($test:expr, $truth:expr, $tol:expr) => { |
| 42 | + $test.rclose($truth, $tol).unwrap(); |
| 43 | + }; |
| 44 | + ($test:expr, $truth:expr, $tol:expr; $comment:expr) => { |
| 45 | + $test.rclose($truth, $tol).expect($comment); |
| 46 | + }; |
42 | 47 | }
|
43 | 48 |
|
44 |
| -macro_rules! impl_AssertAllClose { |
45 |
| - ($scalar:ty, $float:ty, $abs:ident) => { |
46 |
| -impl AssertAllClose for [$scalar]{ |
47 |
| - type Tol = $float; |
48 |
| - fn assert_allclose_inf(&self, truth: &Self, atol: Self::Tol) { |
49 |
| - for (x, y) in self.iter().zip(truth.iter()) { |
50 |
| - let tol = (x - y).$abs(); |
51 |
| - if tol > atol { |
52 |
| - panic!("Not close in inf-norm (atol={}): \ntest = \n{:?}\nTruth = \n{:?}", |
53 |
| - atol, self, truth); |
54 |
| - } |
55 |
| - } |
56 |
| - } |
57 |
| - fn assert_allclose_l2(&self, truth: &Self, rtol: Self::Tol) { |
58 |
| - let nrm: Self::Tol = truth.iter().map(|x| x.$abs().powi(2)).sum(); |
59 |
| - let dev: Self::Tol = self.iter().zip(truth.iter()).map(|(x, y)| (x-y).$abs().powi(2)).sum(); |
60 |
| - if dev / nrm > rtol.powi(2) { |
61 |
| - panic!("Not close in L2-norm (rtol={}): \ntest = \n{:?}\nTruth = \n{:?}", |
62 |
| - rtol, self, truth); |
63 |
| - } |
64 |
| - } |
| 49 | +#[macro_export] |
| 50 | +macro_rules! assert_aclose { |
| 51 | + ($test:expr, $truth:expr, $tol:expr) => { |
| 52 | + $test.aclose($truth, $tol).unwrap(); |
| 53 | + }; |
| 54 | + ($test:expr, $truth:expr, $tol:expr; $comment:expr) => { |
| 55 | + $test.aclose($truth, $tol).expect($comment); |
| 56 | + }; |
65 | 57 | }
|
66 | 58 |
|
67 |
| -impl AssertAllClose for Vec<$scalar> { |
68 |
| - type Tol = $float; |
69 |
| - fn assert_allclose_inf(&self, truth: &Self, atol: Self::Tol) { |
70 |
| - self.as_slice().assert_allclose_inf(&truth, atol); |
71 |
| - } |
72 |
| - fn assert_allclose_l2(&self, truth: &Self, rtol: Self::Tol) { |
73 |
| - self.as_slice().assert_allclose_l2(&truth, rtol); |
74 |
| - } |
| 59 | +/// check two arrays are close in maximum norm |
| 60 | +pub fn all_close_max<A, Tol, S1, S2, D>(test: &ArrayBase<S1, D>, |
| 61 | + truth: &ArrayBase<S2, D>, |
| 62 | + atol: Tol) |
| 63 | + -> Result<Tol, Tol> |
| 64 | + where A: LinalgScalar + Absolute<Output = Tol>, |
| 65 | + Tol: Float + Sum, |
| 66 | + S1: Data<Elem = A>, |
| 67 | + S2: Data<Elem = A>, |
| 68 | + D: Dimension |
| 69 | +{ |
| 70 | + let tol = (test - truth).norm_max(); |
| 71 | + if tol < atol { Ok(tol) } else { Err(tol) } |
75 | 72 | }
|
76 | 73 |
|
77 |
| -impl<D: Dimension> AssertAllClose for Array<$scalar, D> { |
78 |
| - type Tol = $float; |
79 |
| - fn assert_allclose_inf(&self, truth: &Self, atol: Self::Tol) { |
80 |
| - if self.shape() != truth.shape() { |
81 |
| - panic!("Shape missmatch: self={:?}, truth={:?}", self.shape(), truth.shape()); |
82 |
| - } |
83 |
| - for (idx, val) in self.indexed_iter() { |
84 |
| - let t = truth[idx.into_dimension()]; |
85 |
| - let tol = (*val - t).$abs(); |
86 |
| - if tol > atol { |
87 |
| - panic!("Not close in inf-norm (atol={}): \ntest = \n{:?}\nTruth = \n{:?}", |
88 |
| - atol, self, truth); |
89 |
| - } |
90 |
| - } |
91 |
| - } |
92 |
| - fn assert_allclose_l2(&self, truth: &Self, rtol: Self::Tol) { |
93 |
| - if self.shape() != truth.shape() { |
94 |
| - panic!("Shape missmatch: self={:?}, truth={:?}", self.shape(), truth.shape()); |
95 |
| - } |
96 |
| - let nrm: Self::Tol = truth.iter().map(|x| x.$abs().powi(2)).sum(); |
97 |
| - let dev: Self::Tol = self.indexed_iter().map(|(idx, val)| (truth[idx.into_dimension()] - val).$abs().powi(2)).sum(); |
98 |
| - if dev / nrm > rtol.powi(2) { |
99 |
| - panic!("Not close in L2-norm (rtol={}): \ntest = \n{:?}\nTruth = \n{:?}", |
100 |
| - rtol, self, truth); |
101 |
| - } |
102 |
| - } |
| 74 | +/// check two arrays are close in L1 norm |
| 75 | +pub fn all_close_l1<A, Tol, S1, S2, D>(test: &ArrayBase<S1, D>, truth: &ArrayBase<S2, D>, rtol: Tol) -> Result<Tol, Tol> |
| 76 | + where A: LinalgScalar + Absolute<Output = Tol>, |
| 77 | + Tol: Float + Sum, |
| 78 | + S1: Data<Elem = A>, |
| 79 | + S2: Data<Elem = A>, |
| 80 | + D: Dimension |
| 81 | +{ |
| 82 | + let tol = (test - truth).norm_l1() / truth.norm_l1(); |
| 83 | + if tol < rtol { Ok(tol) } else { Err(tol) } |
103 | 84 | }
|
104 |
| -}} // impl_AssertAllClose |
105 | 85 |
|
106 |
| -impl_AssertAllClose!(f64, f64, abs); |
107 |
| -impl_AssertAllClose!(f32, f32, abs); |
108 |
| -impl_AssertAllClose!(Complex<f64>, f64, norm); |
109 |
| -impl_AssertAllClose!(Complex<f32>, f32, norm); |
| 86 | +/// check two arrays are close in L2 norm |
| 87 | +pub fn all_close_l2<A, Tol, S1, S2, D>(test: &ArrayBase<S1, D>, truth: &ArrayBase<S2, D>, rtol: Tol) -> Result<Tol, Tol> |
| 88 | + where A: LinalgScalar + Absolute<Output = Tol>, |
| 89 | + Tol: Float + Sum, |
| 90 | + S1: Data<Elem = A>, |
| 91 | + S2: Data<Elem = A>, |
| 92 | + D: Dimension |
| 93 | +{ |
| 94 | + let tol = (test - truth).norm_l2() / truth.norm_l2(); |
| 95 | + if tol < rtol { Ok(tol) } else { Err(tol) } |
| 96 | +} |
0 commit comments