Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 5 additions & 1 deletion docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ This document explains the changes made to Iris for this release
attribute will be silently changed to "standard". This may cause failures in
code that explicitly checks the calendar attribute. (:pull:`4847`)

#. `@bjlittle`_ accounted for the recent changes in ``python`` >=3.10.0 when
hashing a ``nan``. Specifically, a ``nan`` scalar coordinate will be
promoted to a vector ``nan`` coordinate during cube merge.
Comment thread
stephenworsley marked this conversation as resolved.
Outdated


🚀 Performance Enhancements
===========================
Expand Down Expand Up @@ -249,7 +253,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
35 changes: 2 additions & 33 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,20 +1345,7 @@ 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):
"""
Expand Down
19 changes: 17 additions & 2 deletions lib/iris/tests/integration/merge/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def test_form_contiguous_dimcoord(self):

class TestNaNs(tests.IrisTest):
def test_merge_nan_coords(self):
from sys import version_info

from pkg_resources import parse_version

# 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")
Expand All @@ -50,9 +54,20 @@ def test_merge_nan_coords(self):
cubes = CubeList(cube1.slices_over("x"))
cube2 = cubes.merge_cube()

# Account for change in behaviour for py310+ when hashing a NaN
# Reference https://github.com/SciTools/iris/pull/4874
version = (
f"{version_info.major}.{version_info.minor}.{version_info.micro}"
)
if parse_version(version) >= parse_version("3.10.0"):
# vector coordinate
expected = np.array([np.nan] * cube1.shape[0])
else:
# scalar coordinate
expected = nan_coord1.points

self.assertArrayEqual(
np.isnan(nan_coord1.points),
np.isnan(cube2.coord("nan1").points),
np.isnan(expected), np.isnan(cube2.coord("nan1").points)
)
self.assertArrayEqual(
np.isnan(nan_coord2.points),
Expand Down
18 changes: 0 additions & 18 deletions lib/iris/tests/unit/coords/test_Cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,24 +155,6 @@ def test_nan_other(self):
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):
point = object()
Expand Down