Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,6 @@ This document explains the changes made to Iris for this release
to ``stderr`` when a :class:`~iris.fileformats.cf.CFReader` that fails to
initialise is garbage collected. (:issue:`3312`, :pull:`4646`)

#. `@stephenworsley`_ aligned the behaviour of :obj:`~iris.coords.Cell` equality
to match :obj:`~iris.coords.Coord` equality with respect to NaN values.
Two NaN valued Cells are now considered equal. This fixes :issue:`4681` and
causes NaN valued scalar coordinates to be able to merge be preserved during
cube merging. (:pull:`4701`)

#. `@wjbenfold`_ fixed plotting of circular coordinates to extend kwarg arrays
as well as the data. (:issue:`466`, :pull:`4649`)

Expand Down Expand Up @@ -249,7 +243,7 @@ This document explains the changes made to Iris for this release

#. `@bjlittle`_ and `@jamesp`_ (reviewer) and `@lbdreyer`_ (reviewer) extended
the GitHub Continuous-Integration to cover testing on ``py38``, ``py39``,
and ``py310``. (:pull:`4840` and :pull:`4852`)
and ``py310``. (:pull:`4840`)

#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) adopted `setuptools-scm`_ for
automated ``iris`` package versioning. (:pull:`4841`)
Expand Down
48 changes: 2 additions & 46 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -1266,13 +1266,7 @@ def _get_2d_coord_bound_grid(bounds):
return result


class _Hash:
"""Mutable hash property"""

slots = ("_hash",)


class Cell(namedtuple("Cell", ["point", "bound"]), _Hash):
class Cell(namedtuple("Cell", ["point", "bound"])):
"""
An immutable representation of a single cell of a coordinate, including the
sample point and/or boundary position.
Expand Down Expand Up @@ -1332,18 +1326,6 @@ def __new__(cls, point=None, bound=None):

return super().__new__(cls, point, bound)

def __init__(self, *args, **kwargs):
# Pre-compute the hash value of this instance at creation time based
# on the Cell.point alone. This results in a significant performance
# gain, as Cell.__hash__ is reduced to a minimalist attribute lookup
# for each invocation.
try:
value = 0 if np.isnan(self.point) else hash((self.point,))
except TypeError:
# Passing a string to np.isnan causes this exception.
value = hash((self.point,))
self._hash = value

def __mod__(self, mod):
point = self.point
bound = self.bound
Expand All @@ -1363,48 +1345,22 @@ def __add__(self, mod):
return Cell(point, bound)

def __hash__(self):
# Required for >py39 and >np1.22.x due to changes in Cell behaviour for
# Cell.point=np.nan, as calling super().__hash__() returns a different
# hash each time and thus does not trigger the following call to
# Cell.__eq__ to determine equality.
# Note that, no explicit Cell.bound nan check is performed here.
# That is delegated to Cell.__eq__ instead. It's imperative we keep
# Cell.__hash__ light-weight to minimise performance degradation.
# Also see Cell.__init__ for Cell._hash assignment.
# Reference:
# - https://bugs.python.org/issue43475
# - https://github.com/numpy/numpy/issues/18833
# - https://github.com/numpy/numpy/pull/18908
# - https://github.com/numpy/numpy/issues/21210
return self._hash
return super().__hash__()

def __eq__(self, other):
"""
Compares Cell equality depending on the type of the object to be
compared.

"""

def nan_equality(x, y):
return (
isinstance(x, (float, np.number))
and np.isnan(x)
and isinstance(y, (float, np.number))
and np.isnan(y)
)

if isinstance(other, (int, float, np.number)) or hasattr(
other, "timetuple"
):
if self.bound is not None:
return self.contains_point(other)
elif nan_equality(self.point, other):
return True
else:
return self.point == other
elif isinstance(other, Cell):
if nan_equality(self.point, other.point):
return True
return (self.point == other.point) and (
self.bound == other.bound or self.bound == other.bound[::-1]
)
Expand Down
29 changes: 1 addition & 28 deletions lib/iris/tests/integration/merge/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
# before importing anything else.
import iris.tests as tests # isort:skip

import numpy as np

from iris.coords import AuxCoord, DimCoord
from iris.coords import DimCoord
from iris.cube import Cube, CubeList


Expand All @@ -35,30 +33,5 @@ def test_form_contiguous_dimcoord(self):
self.assertArrayEqual(coord2.bounds, coord1.bounds[::-1, ::-1])


class TestNaNs(tests.IrisTest):
def test_merge_nan_coords(self):
# Test that nan valued coordinates merge together.
cube1 = Cube(np.ones([3, 4]), "air_temperature", units="K")
coord1 = DimCoord([1, 2, 3], long_name="x")
coord2 = DimCoord([0, 1, 2, 3], long_name="y")
nan_coord1 = AuxCoord(np.nan, long_name="nan1")
nan_coord2 = AuxCoord([np.nan] * 4, long_name="nan2")
cube1.add_dim_coord(coord1, 0)
cube1.add_dim_coord(coord2, 1)
cube1.add_aux_coord(nan_coord1)
cube1.add_aux_coord(nan_coord2, 1)
cubes = CubeList(cube1.slices_over("x"))
cube2 = cubes.merge_cube()

self.assertArrayEqual(
np.isnan(nan_coord1.points),
np.isnan(cube2.coord("nan1").points),
)
self.assertArrayEqual(
np.isnan(nan_coord2.points),
np.isnan(cube2.coord("nan2").points),
)


if __name__ == "__main__":
tests.main()
26 changes: 0 additions & 26 deletions lib/iris/tests/unit/coords/test_Cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,32 +146,6 @@ def test_PartialDateTime_other(self):
self.assertNotEqual(cell, PartialDateTime(month=3, hour=12))
self.assertNotEqual(cell, PartialDateTime(month=4))

def test_nan_other(self):
# Check that nans satisfy equality.
cell1 = Cell(np.nan)
cell2 = Cell(np.nan)
self.assertEqual(cell1, np.nan)
self.assertEqual(np.nan, cell1)
self.assertEqual(cell1, cell2)


class Test___hash__(tests.IrisTest):
def test_nan__hash(self):
cell = Cell(np.nan, None)
self.assertEqual(cell._hash, 0)

def test_nan___hash__(self):
cell = Cell(np.nan, None)
self.assertEqual(cell.__hash__(), 0)

def test_non_nan__hash(self):
cell = Cell(1, None)
self.assertNotEqual(cell._hash, 0)

def test_non_nan___hash__(self):
cell = Cell("two", ("one", "three"))
self.assertNotEqual(cell.__hash__(), 0)


class Test_contains_point(tests.IrisTest):
def test_datetimelike_bounded_cell(self):
Expand Down