Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Numpy2] Support for numpy==2.0.0 #2395

Open
wants to merge 58 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8930af0
add numpy 2 from conda-forge dev
valeriupredoi Apr 12, 2024
a3e0632
temp remove PyPI numpy - add numpy 2 from conda-forge dev
valeriupredoi Apr 12, 2024
233eb67
run a GA
valeriupredoi Apr 12, 2024
3687ea0
remove numpy from env
valeriupredoi Apr 12, 2024
bf8dff5
manually install numpy 2.0
valeriupredoi Apr 12, 2024
29284bc
correct conda forge channel
valeriupredoi Apr 15, 2024
23429bc
correct conda forge channel
valeriupredoi Apr 15, 2024
50910b4
do not install manually numpy, like a peasant
valeriupredoi Apr 15, 2024
4d5d1eb
revert to manual installation
valeriupredoi Apr 15, 2024
9fb584a
comment out numpy gubbins i environment yml
valeriupredoi Apr 15, 2024
91a87ed
Merge branch 'main' into numpy2-dev
valeriupredoi May 7, 2024
6eb3d00
Merge branch 'main' into numpy2-dev
valeriupredoi May 10, 2024
ade96ca
Merge branch 'main' into numpy2-dev
valeriupredoi May 13, 2024
ac75b84
Merge branch 'main' into numpy2-dev
valeriupredoi May 27, 2024
d36350a
deactivate direct download
valeriupredoi May 27, 2024
5dfa45a
use direct download again
valeriupredoi May 27, 2024
baedee8
use direct download again
valeriupredoi May 27, 2024
5f91615
Merge branch 'main' into numpy2-dev
valeriupredoi Jun 6, 2024
038099d
Merge branch 'main' into numpy2-dev
valeriupredoi Jun 17, 2024
ccb8f2f
use conda forge package
valeriupredoi Jun 17, 2024
882c1cd
dont install from conda package
valeriupredoi Jun 17, 2024
6d9cda5
restore numpy dep
valeriupredoi Jun 17, 2024
e3e2e25
completeley remove line that installed numpy2 from anaconda package
valeriupredoi Jun 17, 2024
30c99e5
unpin pandas
valeriupredoi Jun 19, 2024
98ee116
unpin pandas
valeriupredoi Jun 19, 2024
0e2799b
release numpy to choose its path
valeriupredoi Jun 19, 2024
03a46fa
replace np.NaN with np.nan
valeriupredoi Jun 19, 2024
af8944e
replace np.NaN with np.nan
valeriupredoi Jun 19, 2024
6a8fe23
fix for new output of np.ogrid
valeriupredoi Jun 19, 2024
e849a73
add printout to test
valeriupredoi Jun 19, 2024
3ec3fe5
explicitly cast Python int
valeriupredoi Jun 20, 2024
26a5228
use vstack instead of simple array
valeriupredoi Jun 20, 2024
ecf68f8
make list out of now tuple
valeriupredoi Jun 20, 2024
c10ce96
explicit cast to numpy strings ffs
valeriupredoi Jun 20, 2024
f822bb8
reqwite the distance stat with sqrt account for masks
valeriupredoi Jun 21, 2024
061a4fb
account for variations in rstol for numpy 2.0
valeriupredoi Jun 21, 2024
a4707ef
add note on sqrt bug
valeriupredoi Jun 21, 2024
266781a
temprary pin numpy<2
valeriupredoi Jun 21, 2024
73d455b
temprary pin numpy<2
valeriupredoi Jun 21, 2024
3007575
unpin and let numpy be two
valeriupredoi Jun 21, 2024
7386b7e
unpin and let numpy be two
valeriupredoi Jun 21, 2024
bf01de0
Merge branch 'main' into numpy2-dev
valeriupredoi Jun 24, 2024
043407a
use the force and xfail the icon test instead
valeriupredoi Jun 24, 2024
a3ccff6
remove commented out pandas pins
valeriupredoi Jun 24, 2024
aefb52c
remove commented out pandas pins
valeriupredoi Jun 24, 2024
1741727
unrun GA
valeriupredoi Jun 24, 2024
4afc351
Update esmvalcore/preprocessor/_compare_with_refs.py
valeriupredoi Jul 3, 2024
6464ecf
Merge branch 'main' into numpy2-dev
valeriupredoi Jul 3, 2024
85b2aa4
remove numpy2 related comment
valeriupredoi Jul 3, 2024
05dea66
remove numpy2 related comment
valeriupredoi Jul 3, 2024
64da79d
Merge branch 'main' into numpy2-dev
valeriupredoi Jul 8, 2024
ea3c702
Merge branch 'main' into numpy2-dev
valeriupredoi Jul 9, 2024
0b2e975
Merge branch 'main' into numpy2-dev
valeriupredoi Aug 5, 2024
b345843
Merge branch 'main' into numpy2-dev
valeriupredoi Sep 13, 2024
55edd79
Merge branch 'main' into numpy2-dev
valeriupredoi Sep 19, 2024
400ccc4
Merge branch 'main' into numpy2-dev
valeriupredoi Sep 27, 2024
e6073eb
run precommit twice
valeriupredoi Sep 27, 2024
803017d
run precommit twice
valeriupredoi Sep 27, 2024
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
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ dependencies:
- nc-time-axis
- nested-lookup
- netcdf4
- numpy !=1.24.3,<2.0.0 # avoid pulling 2.0.0rcX
- numpy !=1.24.3
- packaging
- pandas !=2.2.0,!=2.2.1,!=2.2.2 # github.com/ESMValGroup/ESMValCore/pull/2305 and #2349
- pandas
- pillow
- pip !=21.3
- prov
Expand Down
16 changes: 15 additions & 1 deletion esmvalcore/preprocessor/_compare_with_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,21 @@ def _calculate_rmse(
weights = get_weights(cube, coords) if weighted else None
squared_error = (cube.core_data() - reference.core_data())**2
npx = get_array_module(squared_error)
rmse = npx.sqrt(npx.ma.average(squared_error, axis=axis, weights=weights))

# need masked sqrt for numpy >=2.0
# and consequently dask.array.reductions.safe_sqrt for Dask
# otherwise results will be computed ignoring masks
# see https://github.com/numpy/numpy/issues/25635
# and https://docs.dask.org/en/stable/_modules/dask/array/reductions.html
if npx.__name__ == "dask.array":
da_squared_error = npx.ma.average(squared_error,
axis=axis,
weights=weights)
rmse = npx.reductions.safe_sqrt(da_squared_error)
else:
rmse = npx.ma.sqrt(
npx.ma.average(squared_error, axis=axis, weights=weights)
)
valeriupredoi marked this conversation as resolved.
Show resolved Hide resolved

# Metadata
metadata = CubeMetadata(
Expand Down
2 changes: 1 addition & 1 deletion esmvalcore/preprocessor/_derive/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def _create_pressure_array(cube, ps_cube, top_limit):
ps_4d_array = iris.util.broadcast_to_shape(ps_cube.data, shape, [0, 2, 3])

# Set pressure levels below the surface pressure to NaN
pressure_4d = np.where((ps_4d_array - p_4d_array) < 0, np.NaN, p_4d_array)
pressure_4d = np.where((ps_4d_array - p_4d_array) < 0, np.nan, p_4d_array)

# Make top_limit last pressure level
top_limit_array = np.full(ps_cube.shape, top_limit, dtype=np.float32)
Expand Down
2 changes: 2 additions & 0 deletions esmvalcore/preprocessor/_derive/co2s.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def _get_first_unmasked_data(array, axis):
indices = da.meshgrid(
*[da.arange(array.shape[i]) for i in range(array.ndim) if i != axis],
indexing='ij')
# numpy>=2.0 indices becomes a tuple
schlunma marked this conversation as resolved.
Show resolved Hide resolved
indices = list(indices)
indices.insert(axis, indices_first_positive)
first_unmasked_data = np.array(array)[tuple(indices)]
return first_unmasked_data
Expand Down
5 changes: 4 additions & 1 deletion esmvalcore/preprocessor/_regrid_esmpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ def get_grid(esmpy_lat, esmpy_lon,
num_peri_dims = 1
else:
num_peri_dims = 0
grid = esmpy.Grid(np.array(esmpy_lat.shape),

# previous to numpy 2.0, a np.array(esmpy_lat.shape) was used
# numpy>=2.0 throws ValueError: matrix transpose with ndim<2 is undefined
grid = esmpy.Grid(np.vstack(esmpy_lat.shape),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this backwards-compatible? If yes, do we need this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is, functionally and it returns the same sort of structure, only typed differently

num_peri_dims=num_peri_dims,
staggerloc=[esmpy.StaggerLoc.CENTER])
grid.get_coords(ESMF_LON)[...] = esmpy_lon
Expand Down
7 changes: 5 additions & 2 deletions esmvalcore/preprocessor/_regrid_unstructured.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,11 @@ def _get_weights_and_idx(
src_points_with_convex_hull = self._add_convex_hull_twice(
src_points, hull.vertices
)
src_points_with_convex_hull[-2 * n_hull:-n_hull, 1] -= 360
src_points_with_convex_hull[-n_hull:, 1] += 360
# explicitly casting to int32 since without it, in Numpy 2.0
# one gets OverflowError: Python integer 360 out of bounds for int8
# see notes https://numpy.org/devdocs/numpy_2_0_migration_guide.html
src_points_with_convex_hull[-2 * n_hull:-n_hull, 1] -= np.int32(360)
src_points_with_convex_hull[-n_hull:, 1] += np.int32(360)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work with 360.0 instead of np.int32(360)? The points should be floats, not integers, so to me this makes much more sense. If this works, you can also remove the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, will test - but I agree about points' types indeed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nopers, a float there throws this:

>       src_points_with_convex_hull[-2 * n_hull:-n_hull, 1] -= 360.
E       numpy._core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'subtract' output from dtype('float64') to dtype('int8') with casting rule 'same_kind'

so we either np.int32 the 360 or we np.float the left member


# Actual weights calculation
(weights, indices) = self._calculate_weights(
Expand Down
4 changes: 3 additions & 1 deletion esmvalcore/preprocessor/_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,9 @@ def _transform_to_lst_eager(
"""
# Apart from the time index, all other dimensions will stay the same; this
# is ensured with np.ogrid
idx = np.ogrid[tuple(slice(0, d) for d in data.shape)]
# NOTE: in numpy 2.0 np.ogrid returns ndarray or tuple of ndarrays
schlunma marked this conversation as resolved.
Show resolved Hide resolved
# so item assignment can only be done after converting to a list
idx = list(np.ogrid[tuple(slice(0, d) for d in data.shape)])
time_index = broadcast_to_shape(
time_index, data.shape, (time_dim, lon_dim)
)
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
'nc-time-axis', # needed by iris.plot
'nested-lookup',
'netCDF4',
'numpy!=1.24.3,<2.0.0', # avoid pulling 2.0.0rc1
'numpy!=1.24.3',
'packaging',
'pandas!=2.2.0,!=2.2.1,!=2.2.2', # GH #2305 #2349 etc
'pandas',
'pillow',
'prov',
'psutil',
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/cmor/_fixes/icon/test_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,9 @@ def test_invalid_time_units(cubes_2d):
# Test fix with (sub-)hourly data


# remove xfail when https://github.com/pandas-dev/pandas/issues/57002
# gets fixed; pinning pandas not a viable solution due deps issues
@pytest.mark.xfail(reason='Bug in pandas needs be fixed.')
schlunma marked this conversation as resolved.
Show resolved Hide resolved
def test_hourly_data(cubes_2d):
"""Test fix."""
fix = get_allvars_fix('Amon', 'tas')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
from tests import PreprocessorFile


def assert_allclose(array_1, array_2):
def assert_allclose(array_1, array_2, rtol=1e-7):
"""Assert that (masked) array 1 is close to (masked) array 2."""
if np.ma.is_masked(array_1) or np.ma.is_masked(array_2):
mask_1 = np.ma.getmaskarray(array_1)
mask_2 = np.ma.getmaskarray(array_2)
np.testing.assert_equal(mask_1, mask_2)
np.testing.assert_allclose(array_1[~mask_1], array_2[~mask_2])
np.testing.assert_allclose(array_1[~mask_1], array_2[~mask_2], rtol)
else:
np.testing.assert_allclose(array_1, array_2)
np.testing.assert_allclose(array_1, array_2, rtol)


def products_set_to_dict(products):
Expand Down Expand Up @@ -445,7 +445,10 @@ def test_distance_metric(
out_cube = product_ref.cubes[0]
assert out_cube.shape == ()
assert out_cube.dtype == np.float32
assert_allclose(out_cube.data, ref_data)
# an rtol=1e-6 is needed for numpy >=2.0
assert_allclose(out_cube.data,
np.array(ref_data, dtype=np.float32),
rtol=1e-6)
assert out_cube.var_name == var_name
assert out_cube.long_name == long_name
assert out_cube.standard_name is None
Expand Down Expand Up @@ -620,7 +623,8 @@ def test_distance_metric_masked_data(
expected_data = np.ma.masked_invalid(data)
else:
expected_data = np.array(data, dtype=np.float32)
assert_allclose(out_cube.data, expected_data)
# an rtol=1e-6 is needed for numpy >=2.0
assert_allclose(out_cube.data, expected_data, rtol=1e-6)
assert out_cube.var_name == var_name
assert out_cube.long_name == long_name
assert out_cube.standard_name is None
Expand Down Expand Up @@ -661,6 +665,7 @@ def test_distance_metric_fully_masked_data(
assert out_cube.dtype == np.float64

expected_data = np.ma.masked_all(())
print("out/in/metric", out_cube.data, expected_data, metric)
assert_allclose(out_cube.data, expected_data)
assert out_cube.var_name == var_name
assert out_cube.long_name == long_name
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/preprocessor/_derive/test_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_low_lev_below_surf_press():
"""Test for lowest level below surface pressure."""
plev = 970
top_limit = 5
col = np.array([np.NaN, 900, 800])
col = np.array([np.nan, 900, 800])
col = np.insert(col, 0, plev)
col = np.append(col, top_limit)
result = np.array([0, 120, 845])
Expand All @@ -148,7 +148,7 @@ def test_low_lev_below_surf_press():
assert np.array_equal(_get_pressure_level_widths(col, air_pressure_axis=1),
np.atleast_3d(result))

col = np.array([np.NaN, np.NaN, 900, 800])
col = np.array([np.nan, np.nan, 900, 800])
col = np.insert(col, 0, plev)
col = np.append(col, top_limit)
result = np.array([0, 0, 120, 845])
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/preprocessor/_time/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,9 +920,12 @@ def test_season_not_available(self):
name='clim_season',
seasons=['JFMAMJ', 'JASOND'],
)

# numpy>=2.0 these need to be explicitly cast to numpy strings
two_seasons = [np.str_('JASOND'), np.str_('JFMAMJ')]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is not backwards-compatible? How does the error look like? It might make sense to adapt this in the actual code so the error message still looks nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is, as it is now, ie the test does what it says on the lid for both numpy<2 and numpy>=2, here's the failed test for numpy>=2 if we don't change to forced typing:

>       with pytest.raises(ValueError, match=re.escape(msg)):
E       AssertionError: Regex pattern did not match.
E        Regex: "Seasons\\ \\('DJF',\\ 'MAM',\\ 'JJA',\\ 'SON'\\)\\ do\\ not\\ match\\ prior\\ season\\ extraction\\ \\['JASOND',\\ 'JFMAMJ'\\]\\."
E        Input: "Seasons ('DJF', 'MAM', 'JJA', 'SON') do not match prior season extraction [np.str_('JASOND'), np.str_('JFMAMJ')]."

tests/unit/preprocessor/_time/test_time.py:930: AssertionError

msg = (
"Seasons ('DJF', 'MAM', 'JJA', 'SON') do not match prior season "
"extraction ['JASOND', 'JFMAMJ']."
f"extraction {two_seasons}."
)
with pytest.raises(ValueError, match=re.escape(msg)):
seasonal_statistics(cube, 'mean')
Expand Down