Skip to content

Commit 7408a83

Browse files
authored
Merge 17ca4b8 into 9442894
2 parents 9442894 + 17ca4b8 commit 7408a83

File tree

3 files changed

+212
-134
lines changed

3 files changed

+212
-134
lines changed

lib/iris/tests/unit/concatenate/test_hashing.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import pytest
1010

1111
from iris import _concatenate
12+
from iris.tests.unit.util.test_array_equal import TEST_CASES
13+
from iris.util import array_equal
1214

1315

1416
@pytest.mark.parametrize(
@@ -75,6 +77,20 @@ def test_compute_hashes(a, b, eq):
7577
assert eq == (hashes["a"] == hashes["b"])
7678

7779

80+
@pytest.mark.parametrize(
81+
"a,b",
82+
[
83+
(a, b)
84+
for (a, b, withnans, eq) in TEST_CASES
85+
if isinstance(a, np.ndarray | da.Array) and isinstance(b, np.ndarray | da.Array)
86+
],
87+
)
88+
def test_compute_hashes_vs_array_equal(a, b):
89+
"""Test that hashing give the same answer as `array_equal(withnans=True)`."""
90+
hashes = _concatenate._compute_hashes({"a": a, "b": b})
91+
assert array_equal(a, b, withnans=True) == (hashes["a"] == hashes["b"])
92+
93+
7894
def test_arrayhash_equal_incompatible_chunks_raises():
7995
hash1 = _concatenate._ArrayHash(1, chunks=((1, 1),))
8096
hash2 = _concatenate._ArrayHash(1, chunks=((2,),))

lib/iris/tests/unit/util/test_array_equal.py

Lines changed: 173 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,133 +4,181 @@
44
# See LICENSE in the root of the repository for full licensing details.
55
"""Test function :func:`iris.util.array_equal`."""
66

7+
import dask.array as da
78
import numpy as np
89
import numpy.ma as ma
10+
import pytest
911

1012
from iris.util import array_equal
1113

12-
13-
class Test:
14-
def test_0d(self):
15-
array_a = np.array(23)
16-
array_b = np.array(23)
17-
array_c = np.array(7)
18-
assert array_equal(array_a, array_b)
19-
assert not array_equal(array_a, array_c)
20-
21-
def test_0d_and_scalar(self):
22-
array_a = np.array(23)
23-
assert array_equal(array_a, 23)
24-
assert not array_equal(array_a, 45)
25-
26-
def test_1d_and_sequences(self):
27-
for sequence_type in (list, tuple):
28-
seq_a = sequence_type([1, 2, 3])
29-
array_a = np.array(seq_a)
30-
assert array_equal(array_a, seq_a)
31-
assert not array_equal(array_a, seq_a[:-1])
32-
array_a[1] = 45
33-
assert not array_equal(array_a, seq_a)
34-
35-
def test_nd(self):
36-
array_a = np.array(np.arange(24).reshape(2, 3, 4))
37-
array_b = np.array(np.arange(24).reshape(2, 3, 4))
38-
array_c = np.array(np.arange(24).reshape(2, 3, 4))
39-
array_c[0, 1, 2] = 100
40-
assert array_equal(array_a, array_b)
41-
assert not array_equal(array_a, array_c)
42-
43-
def test_masked_is_not_ignored(self):
44-
array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1])
45-
array_b = ma.masked_array([2, 2, 2], mask=[1, 0, 1])
46-
assert array_equal(array_a, array_b)
47-
48-
def test_masked_is_different(self):
49-
array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1])
50-
array_b = ma.masked_array([1, 2, 3], mask=[0, 0, 1])
51-
assert not array_equal(array_a, array_b)
52-
53-
def test_masked_isnt_unmasked(self):
54-
array_a = np.array([1, 2, 2])
55-
array_b = ma.masked_array([1, 2, 2], mask=[0, 0, 1])
56-
assert not array_equal(array_a, array_b)
57-
58-
def test_masked_unmasked_equivelance(self):
59-
array_a = np.array([1, 2, 2])
60-
array_b = ma.masked_array([1, 2, 2])
61-
array_c = ma.masked_array([1, 2, 2], mask=[0, 0, 0])
62-
assert array_equal(array_a, array_b)
63-
assert array_equal(array_a, array_c)
64-
65-
def test_fully_masked_arrays(self):
66-
array_a = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True)
67-
array_b = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True)
68-
assert array_equal(array_a, array_b)
69-
70-
def test_fully_masked_0d_arrays(self):
71-
array_a = ma.masked_array(3, mask=True)
72-
array_b = ma.masked_array(3, mask=True)
73-
assert array_equal(array_a, array_b)
74-
75-
def test_fully_masked_string_arrays(self):
76-
array_a = ma.masked_array(["a", "b", "c"], mask=True)
77-
array_b = ma.masked_array(["a", "b", "c"], mask=[1, 1, 1])
78-
assert array_equal(array_a, array_b)
79-
80-
def test_partially_masked_string_arrays(self):
81-
array_a = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1])
82-
array_b = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1])
83-
assert array_equal(array_a, array_b)
84-
85-
def test_string_arrays_equal(self):
86-
array_a = np.array(["abc", "def", "efg"])
87-
array_b = np.array(["abc", "def", "efg"])
88-
assert array_equal(array_a, array_b)
89-
90-
def test_string_arrays_different_contents(self):
91-
array_a = np.array(["abc", "def", "efg"])
92-
array_b = np.array(["abc", "de", "efg"])
93-
assert not array_equal(array_a, array_b)
94-
95-
def test_string_arrays_subset(self):
96-
array_a = np.array(["abc", "def", "efg"])
97-
array_b = np.array(["abc", "def"])
98-
assert not array_equal(array_a, array_b)
99-
assert not array_equal(array_b, array_a)
100-
101-
def test_string_arrays_unequal_dimensionality(self):
102-
array_a = np.array("abc")
103-
array_b = np.array(["abc"])
104-
array_c = np.array([["abc"]])
105-
assert not array_equal(array_a, array_b)
106-
assert not array_equal(array_b, array_a)
107-
assert not array_equal(array_a, array_c)
108-
assert not array_equal(array_b, array_c)
109-
110-
def test_string_arrays_0d_and_scalar(self):
111-
array_a = np.array("foobar")
112-
assert array_equal(array_a, "foobar")
113-
assert not array_equal(array_a, "foo")
114-
assert not array_equal(array_a, "foobar.")
115-
116-
def test_nan_equality_nan_ne_nan(self):
117-
array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
118-
array_b = array_a.copy()
119-
assert not array_equal(array_a, array_a)
120-
assert not array_equal(array_a, array_b)
121-
122-
def test_nan_equality_nan_naneq_nan(self):
123-
array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
124-
array_b = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
125-
assert array_equal(array_a, array_a, withnans=True)
126-
assert array_equal(array_a, array_b, withnans=True)
127-
128-
def test_nan_equality_nan_nanne_a(self):
129-
array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
130-
array_b = np.array([1.0, np.nan, 2.0, 0.0, 3.0])
131-
assert not array_equal(array_a, array_b, withnans=True)
132-
133-
def test_nan_equality_a_nanne_b(self):
134-
array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
135-
array_b = np.array([1.0, np.nan, 2.0, np.nan, 4.0])
136-
assert not array_equal(array_a, array_b, withnans=True)
14+
ARRAY1 = np.array(np.arange(24).reshape(2, 3, 4))
15+
ARRAY1[0, 1, 2] = 100
16+
17+
ARRAY2 = np.array([1.0, np.nan, 2.0, np.nan, 3.0])
18+
19+
TEST_CASES = [
20+
# test 0d
21+
(np.array(23), np.array(23), False, True),
22+
(np.array(23), np.array(7), False, False),
23+
# test 0d and scalar
24+
(np.array(23), 23, False, True),
25+
(np.array(23), 45, False, False),
26+
# test 1d and sequences
27+
(np.array([1, 2, 3]), [1, 2, 3], False, True),
28+
(np.array([1, 2, 3]), [1, 2], False, False),
29+
(np.array([1, 45, 3]), [1, 2, 3], False, False),
30+
(np.array([1, 2, 3]), (1, 2, 3), False, True),
31+
(np.array([1, 2, 3]), (1, 2), False, False),
32+
(np.array([1, 45, 3]), (1, 2, 3), False, False),
33+
# test 3d
34+
(
35+
np.array(np.arange(24).reshape(2, 3, 4)),
36+
np.array(np.arange(24).reshape(2, 3, 4)),
37+
False,
38+
True,
39+
),
40+
(
41+
np.array(np.arange(24).reshape(2, 3, 4)),
42+
ARRAY1,
43+
False,
44+
False,
45+
),
46+
# test masked is not ignored
47+
(
48+
ma.masked_array([1, 2, 3], mask=[1, 0, 1]),
49+
ma.masked_array([2, 2, 2], mask=[1, 0, 1]),
50+
False,
51+
True,
52+
),
53+
# test masked is different
54+
(
55+
ma.masked_array([1, 2, 3], mask=[1, 0, 1]),
56+
ma.masked_array([1, 2, 3], mask=[0, 0, 1]),
57+
False,
58+
False,
59+
),
60+
# test masked isn't unmasked
61+
(
62+
np.array([1, 2, 2]),
63+
ma.masked_array([1, 2, 2], mask=[0, 0, 1]),
64+
False,
65+
False,
66+
),
67+
(
68+
np.array([1, 2]),
69+
ma.masked_array([1, 3], mask=[0, 1]),
70+
False,
71+
False,
72+
),
73+
# test masked/unmasked_equivalence
74+
(
75+
np.array([1, 2, 2]),
76+
ma.masked_array([1, 2, 2]),
77+
False,
78+
True,
79+
),
80+
(
81+
np.array([1, 2, 2]),
82+
ma.masked_array([1, 2, 2], mask=[0, 0, 0]),
83+
False,
84+
True,
85+
),
86+
# test fully masked arrays
87+
(
88+
ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True),
89+
ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True),
90+
False,
91+
True,
92+
),
93+
# test fully masked 0d arrays
94+
(
95+
ma.masked_array(3, mask=True),
96+
ma.masked_array(3, mask=True),
97+
False,
98+
True,
99+
),
100+
# test fully masked string arrays
101+
(
102+
ma.masked_array(["a", "b", "c"], mask=True),
103+
ma.masked_array(["a", "b", "c"], mask=[1, 1, 1]),
104+
False,
105+
True,
106+
),
107+
# test partially masked string arrays
108+
(
109+
ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]),
110+
ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]),
111+
False,
112+
True,
113+
),
114+
# test string arrays equal
115+
(
116+
np.array(["abc", "def", "efg"]),
117+
np.array(["abc", "def", "efg"]),
118+
False,
119+
True,
120+
),
121+
# test string arrays different contents
122+
(
123+
np.array(["abc", "def", "efg"]),
124+
np.array(["abc", "de", "efg"]),
125+
False,
126+
False,
127+
),
128+
# test string arrays subset
129+
(
130+
np.array(["abc", "def", "efg"]),
131+
np.array(["abc", "def"]),
132+
False,
133+
False,
134+
),
135+
(
136+
np.array(["abc", "def"]),
137+
np.array(["abc", "def", "efg"]),
138+
False,
139+
False,
140+
),
141+
# test string arrays unequal dimensionality
142+
(np.array("abc"), np.array(["abc"]), False, False),
143+
(np.array(["abc"]), np.array("abc"), False, False),
144+
(np.array("abc"), np.array([["abc"]]), False, False),
145+
(np.array(["abc"]), np.array([["abc"]]), False, False),
146+
# test string arrays 0d and scalar
147+
(np.array("foobar"), "foobar", False, True),
148+
(np.array("foobar"), "foo", False, False),
149+
(np.array("foobar"), "foobar.", False, False),
150+
# test nan equality nan ne nan
151+
(ARRAY2, ARRAY2, False, False),
152+
(ARRAY2, ARRAY2.copy(), False, False),
153+
# test nan equality nan naneq nan
154+
(ARRAY2, ARRAY2, True, True),
155+
(ARRAY2, ARRAY2.copy(), True, True),
156+
# test nan equality nan nanne a
157+
(
158+
np.array([1.0, np.nan, 2.0, np.nan, 3.0]),
159+
np.array([1.0, np.nan, 2.0, 0.0, 3.0]),
160+
True,
161+
False,
162+
),
163+
# test nan equality a nanne b
164+
(
165+
np.array([1.0, np.nan, 2.0, np.nan, 3.0]),
166+
np.array([1.0, np.nan, 2.0, np.nan, 4.0]),
167+
True,
168+
False,
169+
),
170+
]
171+
172+
173+
@pytest.mark.parametrize("lazy", [False, True])
174+
@pytest.mark.parametrize("array_a,array_b,withnans,eq", TEST_CASES)
175+
def test_array_equal(array_a, array_b, withnans, eq, lazy):
176+
if lazy:
177+
identical = array_a is array_b
178+
if isinstance(array_a, np.ndarray):
179+
array_a = da.asarray(array_a)
180+
if isinstance(array_b, np.ndarray):
181+
array_b = da.asarray(array_b)
182+
if identical:
183+
array_b = array_a
184+
assert eq == array_equal(array_a, array_b, withnans=withnans)

lib/iris/util.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -422,16 +422,30 @@ def normalise_array(array):
422422

423423
eq = array1.shape == array2.shape
424424
if eq:
425-
array1_masked = ma.is_masked(array1)
426-
eq = array1_masked == ma.is_masked(array2)
427-
if eq and array1_masked:
428-
eq = np.array_equal(ma.getmaskarray(array1), ma.getmaskarray(array2))
429-
if eq:
430-
eqs = array1 == array2
425+
if is_lazy_data(array1) or is_lazy_data(array2):
426+
data1 = da.ma.getdata(array1)
427+
data2 = da.ma.getdata(array2)
428+
mask1 = da.ma.getmaskarray(array1)
429+
mask2 = da.ma.getmaskarray(array2)
430+
else:
431+
data1 = ma.getdata(array1)
432+
data2 = ma.getdata(array2)
433+
mask1 = ma.getmask(array1)
434+
mask2 = ma.getmask(array2)
435+
if not (mask1 is ma.nomask and mask2 is ma.nomask):
436+
# Ensure masks are of the same type.
437+
mask1 = ma.getmaskarray(array1)
438+
mask2 = ma.getmaskarray(array2)
439+
440+
select = mask1 & mask2
441+
431442
if withnans and (array1.dtype.kind == "f" or array2.dtype.kind == "f"):
432-
eqs = np.where(np.isnan(array1) & np.isnan(array2), True, eqs)
433-
eq = np.all(eqs)
434-
eq = bool(eq) or eq is ma.masked
443+
select |= np.isnan(data1) & np.isnan(data2)
444+
445+
data_eq = np.where(select, True, data1 == data2).all()
446+
mask_eq = (mask1 == mask2).all()
447+
448+
eq = bool(data_eq & mask_eq)
435449

436450
return eq
437451

0 commit comments

Comments
 (0)