Skip to content

Commit 91f16ae

Browse files
authored
Merge pull request #1719 from jsiirola/assert-structured-almost-equal
Rename assertRelativeEqual -> assertStructuredAlmostEqual
2 parents 80636e7 + 8809207 commit 91f16ae

File tree

3 files changed

+145
-89
lines changed

3 files changed

+145
-89
lines changed

pyomo/common/tests/test_unittest.py

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,105 +13,117 @@
1313
import pyomo.common.unittest as unittest
1414

1515
class TestPyomoUnittest(unittest.TestCase):
16-
def test_assertRelativeEqual_comparison(self):
16+
def test_assertStructuredAlmostEqual_comparison(self):
1717
a = 1
1818
b = 1
19-
self.assertRelativeEqual(a, b)
20-
# default relative tolerance is 1e-7. This should have a delta
19+
self.assertStructuredAlmostEqual(a, b)
20+
# default relative tolerance is 1e-7. This should have a reltol
2121
# of "exactly" 1e-7, but due to roundoff error, it is not. The
2222
# choice of 9.999e-8 and 9.999-e7 is specifically so that
2323
# roundoff error doesn't cause tests to fail
2424
b -= 9.999e-8
25-
self.assertRelativeEqual(a, b)
25+
self.assertStructuredAlmostEqual(a, b)
2626
b -= 9.999e-8
2727
with self.assertRaisesRegex(self.failureException, '1 !~= 0.9999'):
28-
self.assertRelativeEqual(a, b)
28+
self.assertStructuredAlmostEqual(a, b)
2929

3030
b = 1
31-
self.assertRelativeEqual(a, b, delta=1e-6)
31+
self.assertStructuredAlmostEqual(a, b, reltol=1e-6)
3232
b -= 9.999e-7
33-
self.assertRelativeEqual(a, b, delta=1e-6)
33+
self.assertStructuredAlmostEqual(a, b, reltol=1e-6)
3434
b -= 9.999e-7
3535
with self.assertRaisesRegex(self.failureException, '1 !~= 0.999'):
36-
self.assertRelativeEqual(a, b, delta=1e-6)
36+
self.assertStructuredAlmostEqual(a, b, reltol=1e-6)
3737

3838
b = 1
39-
self.assertRelativeEqual(a, b, places=6)
39+
self.assertStructuredAlmostEqual(a, b, places=6)
4040
b -= 9.999e-7
41-
self.assertRelativeEqual(a, b, places=6)
41+
self.assertStructuredAlmostEqual(a, b, places=6)
4242
b -= 9.999e-7
4343
with self.assertRaisesRegex(self.failureException, '1 !~= 0.999'):
44-
self.assertRelativeEqual(a, b, places=6)
45-
46-
def test_assertRelativeEqual_errorChecking(self):
47-
with self.assertRaisesRegex(ValueError,
48-
"Cannot specify both places and delta"):
49-
self.assertRelativeEqual(1,1,places=7,delta=1)
50-
51-
def test_assertRelativeEqual_str(self):
52-
self.assertRelativeEqual("hi", "hi")
53-
with self.assertRaisesRegex(self.failureException, "'hi' !~= 'hello'"):
54-
self.assertRelativeEqual("hi", "hello")
55-
with self.assertRaisesRegex(self.failureException, "'hi' !~= \['h',"):
56-
self.assertRelativeEqual("hi", ['h','i'])
57-
58-
def test_assertRelativeEqual_othertype(self):
44+
self.assertStructuredAlmostEqual(a, b, places=6)
45+
46+
with self.assertRaisesRegex(self.failureException, '10 !~= 10.01'):
47+
self.assertStructuredAlmostEqual(10, 10.01, abstol=1e-3)
48+
self.assertStructuredAlmostEqual(10, 10.01, reltol=1e-3)
49+
with self.assertRaisesRegex(self.failureException, '10 !~= 10.01'):
50+
self.assertStructuredAlmostEqual(10, 10.01, delta=1e-3)
51+
52+
def test_assertStructuredAlmostEqual_errorChecking(self):
53+
with self.assertRaisesRegex(
54+
ValueError, "Cannot specify more than one of "
55+
"{places, delta, abstol}"):
56+
self.assertStructuredAlmostEqual(1, 1, places=7, delta=1)
57+
58+
def test_assertStructuredAlmostEqual_str(self):
59+
self.assertStructuredAlmostEqual("hi", "hi")
60+
with self.assertRaisesRegex(
61+
self.failureException, "'hi' !~= 'hello'"):
62+
self.assertStructuredAlmostEqual("hi", "hello")
63+
with self.assertRaisesRegex(
64+
self.failureException, "'hi' !~= \['h',"):
65+
self.assertStructuredAlmostEqual("hi", ['h','i'])
66+
67+
def test_assertStructuredAlmostEqual_othertype(self):
5968
a = datetime.datetime(1,1,1)
6069
b = datetime.datetime(1,1,1)
61-
self.assertRelativeEqual(a, b)
70+
self.assertStructuredAlmostEqual(a, b)
6271
b = datetime.datetime(1,1,2)
63-
with self.assertRaisesRegex(self.failureException,
64-
"datetime.* !~= datetime"):
65-
self.assertRelativeEqual(a, b)
66-
67-
def test_assertRelativeEqual_list(self):
72+
with self.assertRaisesRegex(
73+
self.failureException, "datetime.* !~= datetime"):
74+
self.assertStructuredAlmostEqual(a, b)
75+
76+
def test_assertStructuredAlmostEqual_list(self):
6877
a = [1,2]
6978
b = [1,2,3]
70-
with self.assertRaisesRegex(self.failureException,
71-
'sequences are different sizes \(2 != 3\)'):
72-
self.assertRelativeEqual(a, b)
73-
self.assertRelativeEqual(a, b, allow_second_superset=True)
79+
with self.assertRaisesRegex(
80+
self.failureException,
81+
'sequences are different sizes \(2 != 3\)'):
82+
self.assertStructuredAlmostEqual(a, b)
83+
self.assertStructuredAlmostEqual(a, b, allow_second_superset=True)
7484
a.append(3)
75-
7685

77-
self.assertRelativeEqual(a, b)
86+
87+
self.assertStructuredAlmostEqual(a, b)
7888
b[1] -= 1.999e-7
79-
self.assertRelativeEqual(a, b)
89+
self.assertStructuredAlmostEqual(a, b)
8090
b[1] -= 1.999e-7
8191
with self.assertRaisesRegex(self.failureException, '2 !~= 1.999'):
82-
self.assertRelativeEqual(a, b)
92+
self.assertStructuredAlmostEqual(a, b)
8393

84-
def test_assertRelativeEqual_dict(self):
94+
def test_assertStructuredAlmostEqual_dict(self):
8595
a = {1:2, 3:4}
8696
b = {1:2, 3:4, 5:6}
87-
with self.assertRaisesRegex(self.failureException,
88-
'mappings are different sizes \(2 != 3\)'):
89-
self.assertRelativeEqual(a, b)
90-
self.assertRelativeEqual(a, b, allow_second_superset=True)
97+
with self.assertRaisesRegex(
98+
self.failureException,
99+
'mappings are different sizes \(2 != 3\)'):
100+
self.assertStructuredAlmostEqual(a, b)
101+
self.assertStructuredAlmostEqual(a, b, allow_second_superset=True)
91102
a[5] = 6
92-
93103

94-
self.assertRelativeEqual(a, b)
104+
105+
self.assertStructuredAlmostEqual(a, b)
95106
b[1] -= 1.999e-7
96-
self.assertRelativeEqual(a, b)
107+
self.assertStructuredAlmostEqual(a, b)
97108
b[1] -= 1.999e-7
98109
with self.assertRaisesRegex(self.failureException, '2 !~= 1.999'):
99-
self.assertRelativeEqual(a, b)
110+
self.assertStructuredAlmostEqual(a, b)
100111

101112
del b[1]
102113
b[6] = 6
103-
with self.assertRaisesRegex(self.failureException,
104-
'key \(1\) from first not found in second'):
105-
self.assertRelativeEqual(a, b)
114+
with self.assertRaisesRegex(
115+
self.failureException,
116+
'key \(1\) from first not found in second'):
117+
self.assertStructuredAlmostEqual(a, b)
106118

107-
def test_assertRelativeEqual_nested(self):
119+
def test_assertStructuredAlmostEqual_nested(self):
108120
a = {1.1: [1,2,3], 'a': 'hi', 3: {1:2, 3:4}}
109121
b = {1.1: [1,2,3], 'a': 'hi', 3: {1:2, 3:4}}
110-
self.assertRelativeEqual(a, b)
122+
self.assertStructuredAlmostEqual(a, b)
111123
b[1.1][2] -= 1.999e-7
112124
b[3][1] -= 9.999e-8
113-
self.assertRelativeEqual(a, b)
125+
self.assertStructuredAlmostEqual(a, b)
114126
b[1.1][2] -= 1.999e-7
115127
with self.assertRaisesRegex(self.failureException,
116128
'3 !~= 2.999'):
117-
self.assertRelativeEqual(a, b)
129+
self.assertStructuredAlmostEqual(a, b)

pyomo/common/unittest.py

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,30 @@ class TestCase(_pyunit.TestCase):
3232
additional functionality:
3333
- extended suport for test categories
3434
- additional assertions:
35-
* :py:meth:`assertRelativeEqual`
35+
* :py:meth:`assertStructuredAlmostEqual`
3636
3737
unittest.TestCase documentation
3838
-------------------------------
3939
"""
4040
__doc__ += _unittest.TestCase.__doc__
4141

42-
def assertRelativeEqual(self, first, second,
43-
places=None, msg=None, delta=None,
44-
allow_second_superset=False):
45-
"""Test that first and second are equal up to a relative tolerance
42+
def assertStructuredAlmostEqual(self, first, second,
43+
places=None, msg=None, delta=None,
44+
reltol=None, abstol=None,
45+
allow_second_superset=False):
46+
"""Test that first and second are equal up to a tolerance
4647
47-
This compares first and second using a relative tolerance
48-
(`delta`). It will recursively descend into Sequence and Mapping
49-
containers (allowing for the relative comparison of structured
50-
data including lists and dicts).
48+
This compares first and second using both an absolute (`abstol`) and
49+
relative (`reltol`) tolerance. It will recursively descend into
50+
Sequence and Mapping containers (allowing for the relative
51+
comparison of structured data including lists and dicts).
5152
52-
If `places` is supplied, `delta` is computed as `10**-places`.
53+
`places` and `delta` is supported for compatibility with
54+
assertAlmostEqual. If `places` is supplied, `abstol` is
55+
computed as `10**-places`. `delta` is an alias for `abstol`.
5356
54-
If neither `places` nor `delta` is provided, delta defaults to 1e-7.
57+
If none of {`abstol`, `reltol`, `places`, `delta`} are specified,
58+
`reltol` defaults to 1e-7.
5559
5660
If `allow_second_superset` is True, then:
5761
@@ -67,31 +71,68 @@ def assertRelativeEqual(self, first, second,
6771
`abs(first - second) / max(abs(first), abs(second))`,
6872
only when first != second (thereby avoiding divide-by-zero errors).
6973
74+
Parameters
75+
----------
76+
first:
77+
the first value to compare
78+
second:
79+
the second value to compare
80+
places: int
81+
`first` and `second` are considered equivalent if their
82+
difference is between `places` decimal places; equivalent to
83+
`abstol = 10**-places` (included for compatibility with
84+
assertAlmostEqual)
85+
msg: str
86+
the message to raise on failure
87+
delta: float
88+
alias for `abstol`
89+
abstol: float
90+
the absolute tolerance. `first` and `second` are considered
91+
equivalent if their absolute difference is less than `abstol`
92+
reltol: float
93+
the relative tolerance. `first` and `second` are considered
94+
equivalent if their absolute difference divided by the
95+
larget of `first` and `second` is less than `reltol`
96+
allow_second_superset: bool
97+
If True, then extra entries in containers found on second
98+
will not trigger a failure.
99+
70100
"""
101+
if sum(1 for _ in (places, delta, abstol) if _ is not None) > 1:
102+
raise ValueError("Cannot specify more than one of "
103+
"{places, delta, abstol}")
104+
71105
if places is not None:
72-
if delta is not None:
73-
raise ValueError("Cannot specify both places and delta")
74-
delta = 10**(-places)
75-
if delta is None:
76-
delta = 10**-7
106+
abstol = 10**(-places)
107+
if delta is not None:
108+
abstol = delta
109+
if abstol is None and reltol is None:
110+
reltol = 10**-7
77111

112+
fail = None
78113
try:
79-
self._assertRelativeEqual(
80-
first, second, delta, not allow_second_superset)
114+
self._assertStructuredAlmostEqual(
115+
first, second, abstol, reltol, not allow_second_superset)
81116
except self.failureException as e:
82-
raise self.failureException(self._formatMessage(
117+
fail = self._formatMessage(
83118
msg,
84-
"%s\n Found when comparing with relative tolerance %s:"
85-
"\n first=%s\n second=%s" % (
119+
"%s\n Found when comparing with tolerance "
120+
"(abs=%s, rel=%s):\n"
121+
" first=%s\n second=%s" % (
86122
str(e),
87-
delta,
123+
abstol,
124+
reltol,
88125
_unittest.case.safe_repr(first),
89126
_unittest.case.safe_repr(second),
90-
)))
91-
127+
))
128+
129+
if fail:
130+
raise self.failureException(fail)
92131

93-
def _assertRelativeEqual(self, first, second, delta, exact):
94-
"""Recursive implementation of assertRelativeEqual"""
132+
133+
def _assertStructuredAlmostEqual(self, first, second,
134+
abstol, reltol, exact):
135+
"""Recursive implementation of assertStructuredAlmostEqual"""
95136

96137
args = (first, second)
97138
if all(isinstance(_, Mapping) for _ in args):
@@ -108,8 +149,8 @@ def _assertRelativeEqual(self, first, second, delta, exact):
108149
_unittest.case.safe_repr(key),
109150
))
110151
try:
111-
self._assertRelativeEqual(
112-
first[key], second[key], delta, exact)
152+
self._assertStructuredAlmostEqual(
153+
first[key], second[key], abstol, reltol, exact)
113154
except self.failureException as e:
114155
raise self.failureException(
115156
"%s\n Found when comparing key %s" % (
@@ -130,7 +171,8 @@ def _assertRelativeEqual(self, first, second, delta, exact):
130171
))
131172
for i, (f, s) in enumerate(zip(first, second)):
132173
try:
133-
self._assertRelativeEqual(f, s, delta, exact)
174+
self._assertStructuredAlmostEqual(
175+
f, s, abstol, reltol, exact)
134176
except self.failureException as e:
135177
raise self.failureException(
136178
"%s\n Found at position %s" % (str(e), i))
@@ -142,14 +184,16 @@ def _assertRelativeEqual(self, first, second, delta, exact):
142184
try:
143185
f = float(first)
144186
s = float(second)
145-
if abs(f - s) / max(abs(f), abs(s)) <= delta:
187+
diff = abs(f - s)
188+
if abstol is not None and diff <= abstol:
189+
return # PASS!
190+
if reltol is not None and diff / max(abs(f), abs(s)) <= reltol:
146191
return # PASS!
147192
except:
148193
pass
149194

150195
raise self.failureException(
151-
"%s !~= %s (to relative tolerance %s)" % (
196+
"%s !~= %s" % (
152197
_unittest.case.safe_repr(first),
153198
_unittest.case.safe_repr(second),
154-
delta,
155199
))

pyomo/core/tests/diet/test_diet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_mdb_equality(self):
9999
del dat_results['Solver'][0]['Time']
100100
del db_results['Solver'][0]['Time']
101101
# Compare baselines
102-
self.assertRelativeEqual(dat_results, db_results)
102+
self.assertStructuredAlmostEqual(dat_results, db_results)
103103
os.remove(dat_results_file)
104104
os.remove(db_results_file)
105105

@@ -125,7 +125,7 @@ def test_sqlite_equality(self):
125125
del dat_results['Solver'][0]['Time']
126126
del sqlite_results['Solver'][0]['Time']
127127
# Compare baselines
128-
self.assertRelativeEqual(dat_results, sqlite_results)
128+
self.assertStructuredAlmostEqual(dat_results, sqlite_results)
129129
os.remove(dat_results_file)
130130
os.remove(sqlite_results_file)
131131

0 commit comments

Comments
 (0)