diff --git a/environment.yml b/environment.yml index 1f6ff19..84726c7 100644 --- a/environment.yml +++ b/environment.yml @@ -4,21 +4,24 @@ channels: dependencies: - black - cftime - - dask=2023.11.0 - - esmf=8.4.2=mpi_mpich_h2a0de38_103 - - esmpy=8.4.2 + - dask + - esmf=*=nompi_* + - esmpy - f90nml - flake8 - - gdal=3.9.1 + - gdal - geovista - - iris-esmf-regrid>=0.9 + - hdf5=*=nompi_* + - iris-esmf-regrid=0.15 - iris-sample-data - - iris=3.7.1 + - iris - isort + - libnetcdf!=4.9.1=nompi_* - mo_pack - nccmp + - netcdf-fortran=*=nompi_* - numba - - numpy=1.26.0 + - numpy=2.3.5 - pre-commit - pydata-sphinx-theme - pyflakes @@ -27,11 +30,11 @@ dependencies: - pytest-cov - pytest-xdist - python-stratify - - python=3.10.13 + - python=3.12.12 - ruff - setuptools - setuptools-scm - - sphinx=7.2.6 + - sphinx - sphinx-argparse - sphinx-copybutton - sphinx-sitemap diff --git a/lib/ants/_constraints.py b/lib/ants/_constraints.py index 5c1d2f4..f1bd6ad 100644 --- a/lib/ants/_constraints.py +++ b/lib/ants/_constraints.py @@ -110,7 +110,7 @@ def _bounding_box(target_x, target_y, src_crs): src_crs = source_x.coord_system.as_ants_crs() box = _bounding_box(target_x, target_y, src_crs) - (minx, miny, maxx, maxy) = box.bounds + minx, miny, maxx, maxy = box.bounds slices = utils.cube.get_slices(source, [miny, maxy], [minx, maxx], pad_width) if len(slices) > 2: diff --git a/lib/ants/_version.py b/lib/ants/_version.py index 023d3d3..65b3339 100644 --- a/lib/ants/_version.py +++ b/lib/ants/_version.py @@ -3,4 +3,5 @@ # This file is part of ANTS and is released under the BSD 3-Clause license. # See LICENSE.txt in the root of the repository for full licensing details. """Define the fallback version to be used when ANTS is not installed into an environment.""" # noqa: E501 + FALLBACK_VERSION = "3.2.0dev" diff --git a/lib/ants/analysis/__init__.py b/lib/ants/analysis/__init__.py index cddb441..6641bc0 100644 --- a/lib/ants/analysis/__init__.py +++ b/lib/ants/analysis/__init__.py @@ -29,6 +29,7 @@ `_. """ + import warnings import ants @@ -251,7 +252,7 @@ def merge(primary_cube, alternate_cube, validity_polygon=None): def _flood_fill_neighbour_identify( shape, coords, seed_point, extended_neighbourhood, wraparound ): - (yy, xx) = seed_point + yy, xx = seed_point if yy > 0: coords.add((yy - 1, xx)) if yy < (shape[0] - 1): @@ -320,7 +321,7 @@ def flood_fill( When True, support wraparound in 'x', otherwise stop at the boundary. """ - (y, x) = seed_point + y, x = seed_point if array.ndim != 2: msg = "The provided array should be 2D but that provided is {}D" raise ValueError(msg.format(array.ndim)) @@ -473,7 +474,7 @@ def find_similar_region( identified as similar. """ - (y, x) = seed_point + y, x = seed_point if array.ndim != 2: msg = "The provided array should be 2D but that provided is {}D" raise ValueError(msg.format(array.ndim)) diff --git a/lib/ants/analysis/_merge.py b/lib/ants/analysis/_merge.py index 7a2d025..46e0114 100644 --- a/lib/ants/analysis/_merge.py +++ b/lib/ants/analysis/_merge.py @@ -7,6 +7,7 @@ The metadata is also updated to reflect the analysis made. """ + import abc import logging import warnings diff --git a/lib/ants/cli/ancil_2anc.py b/lib/ants/cli/ancil_2anc.py index 60deaa1..a3ba69e 100755 --- a/lib/ants/cli/ancil_2anc.py +++ b/lib/ants/cli/ancil_2anc.py @@ -30,6 +30,7 @@ `_ """ + import warnings import ants diff --git a/lib/ants/cli/ancil_create_shapefile.py b/lib/ants/cli/ancil_create_shapefile.py index 7081626..245dfd1 100755 --- a/lib/ants/cli/ancil_create_shapefile.py +++ b/lib/ants/cli/ancil_create_shapefile.py @@ -10,6 +10,7 @@ Creates and saves a shapefile from a list of pairs of longitude, latitude points defining a single polygon in a specified polygon file. """ + import argparse import json diff --git a/lib/ants/cli/ancil_fill_n_merge.py b/lib/ants/cli/ancil_fill_n_merge.py index 9bb7cea..773cc78 100755 --- a/lib/ants/cli/ancil_fill_n_merge.py +++ b/lib/ants/cli/ancil_fill_n_merge.py @@ -11,6 +11,7 @@ missing values. """ + import ants import ants.io.save as save import cartopy diff --git a/lib/ants/cli/ancil_general_regrid.py b/lib/ants/cli/ancil_general_regrid.py index 7f6bd86..02866eb 100755 --- a/lib/ants/cli/ancil_general_regrid.py +++ b/lib/ants/cli/ancil_general_regrid.py @@ -28,6 +28,7 @@ is produced, regardless of the number of longitude points in the regrid target. """ + import ants import ants.decomposition as decomp import ants.io.save as save diff --git a/lib/ants/config.py b/lib/ants/config.py index 703fb60..802257c 100644 --- a/lib/ants/config.py +++ b/lib/ants/config.py @@ -49,6 +49,7 @@ see different results). This environment variable is read by cartopy directly. """ + import argparse import configparser import copy diff --git a/lib/ants/coord_systems.py b/lib/ants/coord_systems.py index 58e43bc..d9d16e9 100644 --- a/lib/ants/coord_systems.py +++ b/lib/ants/coord_systems.py @@ -18,6 +18,7 @@ * :func:`ants.regrid.rectilinear` """ + import copy import re from abc import ABCMeta, abstractmethod diff --git a/lib/ants/decomposition.py b/lib/ants/decomposition.py index 1fa8246..4103443 100644 --- a/lib/ants/decomposition.py +++ b/lib/ants/decomposition.py @@ -24,6 +24,7 @@ See :func:`ants.utils.cube.defer_cube`. """ + import itertools import logging import os @@ -172,8 +173,8 @@ def _guess_split(sources, target=None): if not isinstance(target, iris.cube.Cube): target = target[0] dtype = np.promote_types(dtype, target.dtype) - if (np.prod(target.shape) * np.nbytes[dtype]) > ( - np.prod(source.shape) * np.nbytes[dtype] + if (np.prod(target.shape) * np.dtype(dtype).itemsize) > ( + np.prod(source.shape) * np.dtype(dtype).itemsize ): largest_array = target @@ -186,7 +187,7 @@ def _guess_split(sources, target=None): z_elements = np.prod(rem_shape) # Number of points which amount to size_bytes footprint. - n_elements = int((size_bytes) / np.nbytes[dtype]) + n_elements = int((size_bytes) / np.dtype(dtype).itemsize) # Split such that ~square extracts occur for likely optimisation of saving. shape = np.array(largest_array.shape) diff --git a/lib/ants/fileformats/__init__.py b/lib/ants/fileformats/__init__.py index 4e54993..9b3fbda 100644 --- a/lib/ants/fileformats/__init__.py +++ b/lib/ants/fileformats/__init__.py @@ -7,6 +7,7 @@ ancillary generation. These include those supported by iris and additional formats such as grid namelists and raster files. """ + import warnings import ants diff --git a/lib/ants/fileformats/_grid_extract.py b/lib/ants/fileformats/_grid_extract.py index 065bb8e..195c43c 100644 --- a/lib/ants/fileformats/_grid_extract.py +++ b/lib/ants/fileformats/_grid_extract.py @@ -7,6 +7,7 @@ grid via the ants.io.load.load_grid interface. """ + import ants import dask.array as da import iris diff --git a/lib/ants/fileformats/ancil/__init__.py b/lib/ants/fileformats/ancil/__init__.py index 4633fe6..f6bc393 100644 --- a/lib/ants/fileformats/ancil/__init__.py +++ b/lib/ants/fileformats/ancil/__init__.py @@ -21,6 +21,7 @@ cube.attributes['grid_staggering']. """ + import warnings import ants diff --git a/lib/ants/fileformats/ancil/preprocessing.py b/lib/ants/fileformats/ancil/preprocessing.py index 35b1349..eec04a8 100644 --- a/lib/ants/fileformats/ancil/preprocessing.py +++ b/lib/ants/fileformats/ancil/preprocessing.py @@ -17,6 +17,7 @@ #. Create a metadata.ini file for climatology time information (write_metadata_file) """ + import configparser import numbers from collections import Counter diff --git a/lib/ants/fileformats/ancil/template.py b/lib/ants/fileformats/ancil/template.py index 345f769..8b539ec 100644 --- a/lib/ants/fileformats/ancil/template.py +++ b/lib/ants/fileformats/ancil/template.py @@ -7,6 +7,7 @@ Tools for generating the template required for saving ancillary files. """ + import itertools import re @@ -136,7 +137,7 @@ def _set_grid_definition(headers, grid, field): headers["fixed_length_header"]["horiz_grid_type"] = horiz_grid_type # REAL CONSTANTS - (regular_x, regular_y) = field.is_regular + regular_x, regular_y = field.is_regular if regular_x: headers["real_constants"]["col_spacing"] = field.bdx # Longitude of first column in degrees (longitudes in range 0-360) diff --git a/lib/ants/fileformats/ancil/time_headers.py b/lib/ants/fileformats/ancil/time_headers.py index 67e60a8..7b01665 100644 --- a/lib/ants/fileformats/ancil/time_headers.py +++ b/lib/ants/fileformats/ancil/time_headers.py @@ -7,6 +7,7 @@ This module provides functions related to time handling for ancillary files. """ + import itertools import ants diff --git a/lib/ants/fileformats/namelist/__init__.py b/lib/ants/fileformats/namelist/__init__.py index a23fbc6..fcc42cb 100644 --- a/lib/ants/fileformats/namelist/__init__.py +++ b/lib/ants/fileformats/namelist/__init__.py @@ -6,6 +6,7 @@ Module for reading Fortran namelist files and constructing Python or Iris objects, as appropriate, from the contents. """ + import warnings import ants diff --git a/lib/ants/fileformats/namelist/umgrid.py b/lib/ants/fileformats/namelist/umgrid.py index efad835..4128319 100644 --- a/lib/ants/fileformats/namelist/umgrid.py +++ b/lib/ants/fileformats/namelist/umgrid.py @@ -22,6 +22,7 @@ definition specification. """ + from abc import ABCMeta, abstractproperty from collections import namedtuple @@ -642,7 +643,7 @@ def __init__(self, namelist_dict): _eta_rho.insert(0, 0.0) # rho level above model top not in namelist. Instead, derived after # conversion to self._brlev, so use NAN as a placeholder: - _eta_rho.append(np.NAN) + _eta_rho.append(np.nan) self._eta_rho = np.array(_eta_rho, dtype=np.float64) # brlev defines level_height.lower bounds diff --git a/lib/ants/fileformats/netcdf/__init__.py b/lib/ants/fileformats/netcdf/__init__.py index 20a16b1..70bb85b 100644 --- a/lib/ants/fileformats/netcdf/__init__.py +++ b/lib/ants/fileformats/netcdf/__init__.py @@ -3,6 +3,7 @@ # This file is part of ANTS and is released under the BSD 3-Clause license. # See LICENSE.txt in the root of the repository for full licensing details. """The entry point for netCDF saving is via :func:`ants.io.save.netcdf`.""" + from . import cf, ukca __all__ = [ diff --git a/lib/ants/fileformats/pp/__init__.py b/lib/ants/fileformats/pp/__init__.py index 74197cb..5fa7f93 100644 --- a/lib/ants/fileformats/pp/__init__.py +++ b/lib/ants/fileformats/pp/__init__.py @@ -15,6 +15,7 @@ 1. Pseudo-level order from the PP file is preserved. """ + import collections import itertools diff --git a/lib/ants/fileformats/raster.py b/lib/ants/fileformats/raster.py index f826090..a628324 100644 --- a/lib/ants/fileformats/raster.py +++ b/lib/ants/fileformats/raster.py @@ -9,6 +9,7 @@ information. """ + import copy import warnings @@ -64,7 +65,16 @@ def __init__(self, shape, dtype, path, raster_band_index, fill_value): # the saving of netcdf3 output properly (auto type-casting etc.) dtype = np.dtype(dtype) if dtype.name.startswith("uint"): - dtype = np.dtype(np.sctypeDict[dtype.num + 1]) + if (dtype.name) == "uint8": + dtype = np.dtype("int16") + elif (dtype.name) == "uint16": + dtype = np.dtype("int32") + elif (dtype.name) == "uint32": + dtype = np.dtype("int64") + elif (dtype.name) == "uint64": + dtype = np.dtype("int128") + else: + raise TypeError(f"Cannot cast {dtype.name} from unsigned to signed int") self.dtype = dtype self.path = path self.raster_band_index = raster_band_index diff --git a/lib/ants/io/load.py b/lib/ants/io/load.py index 9c53786..089e828 100644 --- a/lib/ants/io/load.py +++ b/lib/ants/io/load.py @@ -55,6 +55,7 @@ :func:`ants.fileformats.namelist.load_um_vertical` """ + import copy import warnings from contextlib import contextmanager diff --git a/lib/ants/io/save.py b/lib/ants/io/save.py index 9cbc83e..5f9fdef 100644 --- a/lib/ants/io/save.py +++ b/lib/ants/io/save.py @@ -20,6 +20,7 @@ specifying ``saver='ukca'`` (see :func:`ants.io.save.ukca_netcdf`). """ + import os import sys import warnings diff --git a/lib/ants/regrid/__init__.py b/lib/ants/regrid/__init__.py index f658195..2ba6c7c 100644 --- a/lib/ants/regrid/__init__.py +++ b/lib/ants/regrid/__init__.py @@ -17,6 +17,7 @@ For further details see the user guide. """ + import logging import sys diff --git a/lib/ants/regrid/esmf.py b/lib/ants/regrid/esmf.py index 4b7954f..f3075e5 100644 --- a/lib/ants/regrid/esmf.py +++ b/lib/ants/regrid/esmf.py @@ -412,33 +412,27 @@ def __init__(self, src_cube, target_cube, **kwargs): if keywarg_diff: msg = "unexpected keyword argument {}" raise ValueError(msg.format(keywarg_diff)) - if esmpy is None: raise _ESMPY_IMPORT_ERROR _supported_cube_check(src_cube) _supported_cube_check(target_cube) - # Set some parameters. self.handle = None self.coordSystem = esmpy.api.constants.CoordSys.SPH_DEG self.method = esmpy.api.constants.RegridMethod.CONSERVE self.stagger = esmpy.StaggerLoc.CENTER - method = kwargs.get("method", "areaweighted") if method.lower() != "areaweighted": raise ValueError("Currently only area weighted regridding " "supported.") - # Simply return if the src and tgt grids are identical. if (src_cube.coord(axis="x") == target_cube.coord(axis="x")) and ( src_cube.coord(axis="y") == target_cube.coord(axis="y") ): return _source_cube_sanity_check(src_cube) - # Build the 2D esmf grid and field objects. self.esmpy_src_grid, self.esmpy_src_field = self._build_field(src_cube) self.esmpy_tgt_grid, self.esmpy_tgt_field = self._build_field(target_cube) - # Compute/read the weights following ESMPy weights tutorial. See # ESMPy docs for details of arguments. self._cache_fnme = self._gen_cache_filename([src_cube, target_cube]) @@ -872,7 +866,6 @@ def _build_field(self, cube): # # Build the esmpy field object. # - staggering = "corner" if self.method != esmpy.api.constants.RegridMethod.CONSERVE: # Need to pass corner coordinates in all cases. When the field is @@ -884,20 +877,16 @@ def _build_field(self, cube): extractor = _LatLonExtractor(cube, staggering) lats = extractor.get_latitude() lons = extractor.get_longitude() - # Create the grid. cellDims = np.array([lons.shape[0] - 1, lats.shape[1] - 1]) grid = esmpy.Grid(max_index=cellDims, coord_sys=self.coordSystem) - # Allocate space for the vertices, esmpy wants the first coordinate to # be longitudes. grid.add_coords(staggerloc=esmpy.StaggerLoc.CORNER, coord_dim=0) # No need to add lats, it will be added automatically with lons - # Get pointers to the esmf coordinates. lonPoint = grid.get_coords(coord_dim=0, staggerloc=esmpy.StaggerLoc.CORNER) latPoint = grid.get_coords(coord_dim=1, staggerloc=esmpy.StaggerLoc.CORNER) - # When esmpy runs in parallel, the start/end indices may be other than # 0,-1. # CP: Is esmpy running in parallel being tested?? I suggest just @@ -909,7 +898,6 @@ def _build_field(self, cube): iend0 = grid.upper_bounds[esmpy.StaggerLoc.CORNER][0] ibeg1 = grid.lower_bounds[esmpy.StaggerLoc.CORNER][1] iend1 = grid.upper_bounds[esmpy.StaggerLoc.CORNER][1] - lonPoint[...] = lons[ibeg0:iend0, ibeg1:iend1] latPoint[...] = lats[ibeg0:iend0, ibeg1:iend1] diff --git a/lib/ants/tests/command_parse/test_integration.py b/lib/ants/tests/command_parse/test_integration.py index 31105ab..0662499 100644 --- a/lib/ants/tests/command_parse/test_integration.py +++ b/lib/ants/tests/command_parse/test_integration.py @@ -5,6 +5,7 @@ """ Stub doc for testing. """ + import argparse import unittest.mock as mock diff --git a/lib/ants/tests/decomposition/test_integration.py b/lib/ants/tests/decomposition/test_integration.py index 2f7cb3a..a866cc6 100644 --- a/lib/ants/tests/decomposition/test_integration.py +++ b/lib/ants/tests/decomposition/test_integration.py @@ -9,6 +9,7 @@ suitable for multiprocessing or any parallelism framework). """ + import os import unittest.mock as mock diff --git a/lib/ants/tests/fileformats/ancil/test_integration.py b/lib/ants/tests/fileformats/ancil/test_integration.py index b051219..7b59706 100644 --- a/lib/ants/tests/fileformats/ancil/test_integration.py +++ b/lib/ants/tests/fileformats/ancil/test_integration.py @@ -33,7 +33,7 @@ def _make_global_cube(self, shift, nlat=None, nlon=None, data=None): elif nlat is not None and nlon is not None: raise RuntimeError("Overspecified, both data and 'nlat/nlon' " "specified") else: - (nlat, nlon) = data.shape[-2:] + nlat, nlon = data.shape[-2:] # Mask an element since we lose the fill_value when the array is not # masked. if np.ma.isMaskedArray(data): diff --git a/lib/ants/tests/fileformats/ancil/test_load_um_cubes.py b/lib/ants/tests/fileformats/ancil/test_load_um_cubes.py index 49d1bc6..fdcf4fa 100644 --- a/lib/ants/tests/fileformats/ancil/test_load_um_cubes.py +++ b/lib/ants/tests/fileformats/ancil/test_load_um_cubes.py @@ -26,7 +26,7 @@ def setUp(self): def test_iris_call(self): load_cubes(mock.sentinel.dummy) - self.mock_callback.called_once_with(self.grid_staggering) + self.mock_callback.assert_called_once_with(self.grid_staggering) self.mock_load.assert_called_once_with( mock.sentinel.dummy, self.mock_callback() ) diff --git a/lib/ants/tests/fileformats/ancil/test_load_um_cubes_32bit_ieee.py b/lib/ants/tests/fileformats/ancil/test_load_um_cubes_32bit_ieee.py index 4e7c9dd..129955a 100644 --- a/lib/ants/tests/fileformats/ancil/test_load_um_cubes_32bit_ieee.py +++ b/lib/ants/tests/fileformats/ancil/test_load_um_cubes_32bit_ieee.py @@ -26,7 +26,7 @@ def setUp(self): def test_iris_call(self): load_cubes_32bit_ieee(mock.sentinel.dummy) - self.mock_callback.called_once_with(self.grid_staggering) + self.mock_callback.assert_called_once_with(self.grid_staggering) self.mock_load.assert_called_once_with( mock.sentinel.dummy, self.mock_callback() ) diff --git a/lib/ants/tests/fileformats/namelist/test_CAPGridRegular.py b/lib/ants/tests/fileformats/namelist/test_CAPGridRegular.py index fb66d5e..4f24dbf 100644 --- a/lib/ants/tests/fileformats/namelist/test_CAPGridRegular.py +++ b/lib/ants/tests/fileformats/namelist/test_CAPGridRegular.py @@ -55,7 +55,7 @@ def test_all(self): grid = CAPGrid( {"grid": {"lambda_origin_targ": targ_x, "phi_origin_targ": targ_y}} ) - (res_y, res_x) = grid._start_yx + res_y, res_x = grid._start_yx self.assertIs(res_x, targ_x) self.assertIs(res_y, targ_y) @@ -65,7 +65,7 @@ def test_number_of_points_defined(self): # Shape determined directly sample = {"grid": {"points_lambda_targ": 30, "points_phi_targ": 30}} grid = CAPGrid(sample) - (res_y, res_x) = grid.shape + res_y, res_x = grid.shape self.assertEqual(res_x, 30) self.assertEqual(res_y, 30) @@ -80,7 +80,7 @@ def test_inferred_global_grid_newdynamics(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid.shape + res_y, res_x = grid.shape self.assertIs(res_x, 12) self.assertIs(res_y, 7) @@ -94,7 +94,7 @@ def test_inferred_global_grid_endgame(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid.shape + res_y, res_x = grid.shape self.assertIs(res_x, 12) self.assertIs(res_y, 6) @@ -117,7 +117,7 @@ class Test__step_yx(ants.tests.TestCase): def test_explicit_definition(self): sample = {"grid": {"delta_phi_targ": 30, "delta_lambda_targ": 30}} grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_x, 30) self.assertEqual(res_y, -30) @@ -134,7 +134,7 @@ def test_implicit_global_definition_newdynamics(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_x, 12) self.assertEqual(res_y, -2.5) @@ -150,7 +150,7 @@ def test_implicit_global_definition_endgame(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_x, 12) self.assertEqual(res_y, -2.5) @@ -185,7 +185,7 @@ def test_consistent_overspecified_lambda(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_x, 12) def test_contradictory_overspecified_phi_endgame(self): @@ -214,7 +214,7 @@ def test_consistent_overspecified_phi_endgame(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_y, -2.5) def test_contradictory_overspecified_phi_newdynamics(self): @@ -243,7 +243,7 @@ def test_consistent_overspecified_phi_newdynamics(self): } } grid = CAPGrid(sample) - (res_y, res_x) = grid._step_yx + res_y, res_x = grid._step_yx self.assertEqual(res_y, -2.5) diff --git a/lib/ants/tests/regrid/interpolation/test_integration.py b/lib/ants/tests/regrid/interpolation/test_integration.py index 05e8b92..fc5e7bb 100644 --- a/lib/ants/tests/regrid/interpolation/test_integration.py +++ b/lib/ants/tests/regrid/interpolation/test_integration.py @@ -291,32 +291,33 @@ def test_target_interpolation_coord_as_aux_coord(self): self.assertTrue(len(res.coords("model_level_number", dim_coords=True)) > 0) -class TestCoordinates(_Common): - # This class deliberately does not inherit from ants.tests.TestCase. This - # means we can use pytest parametrize. - @pytest.mark.parametrize( - "name", - [ - ("model_level_number"), - ("latitude"), - ("longitude"), - ("level_height"), - ("sigma"), - ("surface_altitude"), - ("altitude"), - ], - ) - def test_neq_2d_return_coordinates(self, name): - """Ensure that all the spatial coordinates on the result are correct. - - Correct in this case means the same as on the target.""" - self.setUp() # Needed because this class is not a unittest.TestCase. - expected = self.target.coord(name) - - result = self.source.regrid(self.target, interpolation.Linear()) - actual = result.coord(name) - - assert actual == expected +@pytest.mark.parametrize( + "name", + [ + ("model_level_number"), + ("latitude"), + ("longitude"), + ("level_height"), + ("sigma"), + ("surface_altitude"), + ("altitude"), + ], +) +def test_neq_2d_return_coordinates(name): + """Ensure that all the spatial coordinates on the result are correct. + + Correct in this case means the same as on the target.""" + source = stock.simple_4d_with_hybrid_height()[0:1] + ants.utils.cube.set_crs(source, ants.coord_systems.UM_SPHERE) + ants.utils.cube.guess_horizontal_bounds(source) + target = source.copy()[0] + + expected = target.coord(name) + + result = source.regrid(target, interpolation.Linear()) + actual = result.coord(name) + + assert actual == expected @ants.tests.skip_stratify diff --git a/lib/ants/tests/regrid/rectilinear/test__fill_outside_bounds.py b/lib/ants/tests/regrid/rectilinear/test__fill_outside_bounds.py index a22430d..8930fe1 100644 --- a/lib/ants/tests/regrid/rectilinear/test__fill_outside_bounds.py +++ b/lib/ants/tests/regrid/rectilinear/test__fill_outside_bounds.py @@ -42,7 +42,7 @@ def test_decreasing_source(self): sy = self.source.coord(axis="y") self._invert_coord(sy) - actual = _fill_outside_bounds(self.source, self.target, np.NaN) + actual = _fill_outside_bounds(self.source, self.target, np.nan) self.assertArrayEqual(actual.data, self.result) def test_decreasing_target(self): @@ -55,7 +55,7 @@ def test_decreasing_target(self): ty = self.target.coord(axis="y") self._invert_coord(ty) - actual = _fill_outside_bounds(self.source, self.target, np.NaN) + actual = _fill_outside_bounds(self.source, self.target, np.nan) self.assertArrayEqual(actual.data, self.result[::-1, ::-1]) def test_masked(self): @@ -63,7 +63,7 @@ def test_masked(self): # unmasked while masked elements inside the extent remain masked. self.target.data = np.ma.array(self.target.data) self.target.data[0, :] = np.ma.masked - actual = _fill_outside_bounds(self.source, self.target, np.NaN) + actual = _fill_outside_bounds(self.source, self.target, np.nan) expected = np.ma.array(self.result) expected[0, 1:3] = np.ma.masked @@ -83,7 +83,7 @@ def test_zyx_source(self): self.source = iris.cube.CubeList([cube1, cube2]).merge_cube() - actual = _fill_outside_bounds(self.source, self.target, np.NaN) + actual = _fill_outside_bounds(self.source, self.target, np.nan) self.assertArrayEqual(actual.data, self.result) def test_xzy(self): @@ -102,7 +102,7 @@ def test_xzy(self): self.target.transpose((2, 0, 1)) self.result = self.result.transpose((2, 0, 1)) - actual = _fill_outside_bounds(self.source, self.target, np.NaN) + actual = _fill_outside_bounds(self.source, self.target, np.nan) self.assertArrayEqual(actual.data, self.result) diff --git a/lib/ants/tests/regrid/test_integration.py b/lib/ants/tests/regrid/test_integration.py index e1be620..e930f5b 100644 --- a/lib/ants/tests/regrid/test_integration.py +++ b/lib/ants/tests/regrid/test_integration.py @@ -18,11 +18,8 @@ def test_no_scheme_given(self): with self.assertRaises(AttributeError) as context: source.regrid(target, scheme) - self.assertTrue( - "At least one of horizontal \ - or vertical re-grid schemes must be provided." - in context.exception - ) + self.assertTrue("At least one of horizontal \ + or vertical re-grid schemes must be provided." in context.exception) class TestInterpolation(ants.tests.TestCase): diff --git a/lib/ants/tests/test_dependencies.py b/lib/ants/tests/test_dependencies.py index d6f6c65..9aa0c9d 100644 --- a/lib/ants/tests/test_dependencies.py +++ b/lib/ants/tests/test_dependencies.py @@ -18,6 +18,7 @@ skipped if the import fails via the appropriate decorator @ants.tests.skip_, e.g. @ants.tests.skip_mule. """ # noqa: E501 + import ants # noqa: F401 diff --git a/lib/ants/tests/utils/_dask/test_as_lazy_data.py b/lib/ants/tests/utils/_dask/test_as_lazy_data.py index c9fe673..593ab24 100644 --- a/lib/ants/tests/utils/_dask/test_as_lazy_data.py +++ b/lib/ants/tests/utils/_dask/test_as_lazy_data.py @@ -5,6 +5,7 @@ from unittest import mock import ants.tests +import numpy as np from ants.utils._dask import as_lazy_data @@ -14,6 +15,8 @@ def test_check_iris_as_lazy_data_spec(self): # we expect: We have to do this because it's private. patch = mock.patch("iris._lazy_data.as_lazy_data", spec_set=True) with patch as patched: + mock.sentinel.data.shape = 3 + mock.sentinel.data.dtype = np.int16 as_lazy_data( mock.sentinel.data, chunks=mock.sentinel.chunks, diff --git a/lib/ants/tests/utils/cube/test_guess_horizontal_bounds.py b/lib/ants/tests/utils/cube/test_guess_horizontal_bounds.py index 0e02d4e..5a57b7a 100644 --- a/lib/ants/tests/utils/cube/test_guess_horizontal_bounds.py +++ b/lib/ants/tests/utils/cube/test_guess_horizontal_bounds.py @@ -21,14 +21,23 @@ def setUp(self): self.addCleanup(patch.stop) def test_single_cube(self): + """Test that arguments are being passed in correctly with a single cube as + input""" cube = mock.Mock(name="cube", spec_set=iris.cube.Cube) guess_horizontal_bounds(cube) - self.mock_hgrid.called_once_with(cube) - self.mock_guess.called_once_with(mock.sentinel.x, mock.sentinel.y) + self.mock_hgrid.assert_called_once_with(cube) + assert ( + mock.call(mock.sentinel.x, strict=False) in self.mock_guess.call_args_list + ) + assert ( + mock.call(mock.sentinel.y, strict=False) in self.mock_guess.call_args_list + ) def test_multi_cube(self): + """Test that both cubes are used when a cubelist is passed into + guess_horizontal_bounds""" cube = mock.Mock(name="cube", spec_set=iris.cube.Cube) cube2 = mock.Mock(name="cube2", spec_set=iris.cube.Cube) guess_horizontal_bounds([cube, cube2]) - self.mock_hgrid.called_with(cube) - self.mock_hgrid.called_with(cube2) + assert mock.call(cube) in self.mock_hgrid.call_args_list + assert mock.call(cube2) in self.mock_hgrid.call_args_list diff --git a/lib/ants/tests/utils/cube/test_inherit_metadata.py b/lib/ants/tests/utils/cube/test_inherit_metadata.py index 7214917..82cc5d1 100644 --- a/lib/ants/tests/utils/cube/test_inherit_metadata.py +++ b/lib/ants/tests/utils/cube/test_inherit_metadata.py @@ -15,9 +15,8 @@ def testall(self): target.name.return_value = mock.sentinel.name target.units = mock.sentinel.units target.attributes["grid_staggering"] = mock.sentinel.grid_staggering - inherit_metadata(source, target) - self.assertTrue(source.rename.called_once_with(mock.sentinel.name)) + source.rename.assert_called_once_with(mock.sentinel.name) self.assertIs(source.units, target.units) self.assertIs( source.attributes["grid_staggering"], target.attributes["grid_staggering"] diff --git a/lib/ants/utils/_dask.py b/lib/ants/utils/_dask.py index 1e7f351..60f153c 100644 --- a/lib/ants/utils/_dask.py +++ b/lib/ants/utils/_dask.py @@ -6,6 +6,7 @@ import dask import iris +import numpy as np _LOGGER = logging.getLogger(__name__) @@ -65,7 +66,8 @@ def as_lazy_data(data, chunks=None, asarray=False): This is for ants core library usage ONLY! """ - return iris._lazy_data.as_lazy_data(data, chunks=chunks, asarray=asarray) + meta = np.empty(data.shape, data.dtype) + return iris._lazy_data.as_lazy_data(data, chunks=chunks, asarray=asarray, meta=meta) def _is_masked_array(dask_array): diff --git a/pyproject.toml b/pyproject.toml index a0d1632..fe2b145 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,15 @@ filterwarnings = [ "ignore:Unable to import mule:UserWarning", # Warning for the optional dependency of um_spiral_search (from shumlib) not being present: - "ignore:No module named 'um_spiral_search':UserWarning" + "ignore:No module named 'um_spiral_search':UserWarning", + + # Warning for a deprecation within Mule: + "ignore::DeprecationWarning", + + #iris FutureWarning + "ignore::FutureWarning:iris", + + "ignore:Saving to netcdf with legacy-style attribute handling for backwards compatibility." # Additional filters are defined in ./lib/ants/tests/conftest.py, including: # 1. Warning when catopy downloads and caches Natural Earth data. diff --git a/rose-stem/flow.cylc b/rose-stem/flow.cylc index 48941eb..dd33f1d 100644 --- a/rose-stem/flow.cylc +++ b/rose-stem/flow.cylc @@ -1,5 +1,5 @@ #!jinja2 -{% set ANTS_MODULE = "ants/developer" %} +{% set ANTS_MODULE = "ants/developer-68" %} {% set PYTHONPATH_PREPEND = "$CYLC_WORKFLOW_RUN_DIR/share/fcm_make_ants/build/lib" %} {% set fill_n_merge_source = ['land_cover', 'invert_mask'] %} diff --git a/utils/generate_logs/conftest.py b/utils/generate_logs/conftest.py index 943aeb0..3b7cf75 100644 --- a/utils/generate_logs/conftest.py +++ b/utils/generate_logs/conftest.py @@ -54,11 +54,9 @@ def create_task_jobs_table(cursor: sqlite3.Cursor) -> None: An sqlite3 Cursor connected to a database. """ - cursor.execute( - """CREATE TABLE IF NOT EXISTS task_jobs (name TEXT NOT NULL, + cursor.execute("""CREATE TABLE IF NOT EXISTS task_jobs (name TEXT NOT NULL, time_run TEXT NOT NULL, time_run_exit TEXT NOT NULL, - submit_num TEXT NOT NULL)""" - ) + submit_num TEXT NOT NULL)""") rows_to_insert_as_tuples = [ (row["name"], row["time_run"], row["time_run_exit"], row["submit_num"]) for row in SYNTHETIC_TASK_DATA @@ -88,10 +86,8 @@ def create_task_states_table(cursor: sqlite3.Cursor) -> None: An sqlite3 Cursor connected to a database. """ - cursor.execute( - """CREATE TABLE IF NOT EXISTS task_states - (name TEXT NOT NULL, submit_num TEXT NOT NULL)""" - ) + cursor.execute("""CREATE TABLE IF NOT EXISTS task_states + (name TEXT NOT NULL, submit_num TEXT NOT NULL)""") rows_to_insert_as_tuples = [ (row["name"], row["submit_num"]) for row in SYNTHETIC_TASK_DATA diff --git a/utils/generate_logs/durations_extract_format.py b/utils/generate_logs/durations_extract_format.py index 2710167..43ba8d2 100644 --- a/utils/generate_logs/durations_extract_format.py +++ b/utils/generate_logs/durations_extract_format.py @@ -32,6 +32,7 @@ DataTable: Helper dataclass for storing table elements. """ + import sqlite3 from dataclasses import dataclass from datetime import datetime, timedelta diff --git a/utils/generate_logs/durations_logger.py b/utils/generate_logs/durations_logger.py index 316c273..6fc905a 100644 --- a/utils/generate_logs/durations_logger.py +++ b/utils/generate_logs/durations_logger.py @@ -9,6 +9,7 @@ line flag ``verbosity``, which calls ``set_console_handler_log_level`` in ``main.py``. """ + import logging import sys from typing import TextIO diff --git a/utils/generate_logs/durations_main.py b/utils/generate_logs/durations_main.py index cb4f7d2..ecfcaa3 100755 --- a/utils/generate_logs/durations_main.py +++ b/utils/generate_logs/durations_main.py @@ -9,6 +9,7 @@ function argparse ``type`` validator functions and a ``validate_sql_lite_db`` function. """ + import argparse import logging import sqlite3 diff --git a/utils/generate_logs/test_durations_extract_format.py b/utils/generate_logs/test_durations_extract_format.py index ff63adb..130a3ff 100644 --- a/utils/generate_logs/test_durations_extract_format.py +++ b/utils/generate_logs/test_durations_extract_format.py @@ -249,11 +249,9 @@ def test_extract_driver_for_an_invalid_cylc_8_database_source(self): non_cylc_eight_db = Path(temp_directory) / "non_cylc_eight_db" connection = sqlite3.connect(non_cylc_eight_db) cursor = connection.cursor() - cursor.execute( - """CREATE TABLE IF NOT EXISTS task_jobs + cursor.execute("""CREATE TABLE IF NOT EXISTS task_jobs (name TEXT NOT NULL, time_run TEXT NOT NULL, submit_num TEXT NOT NULL) - """ - ) + """) connection.close() table_maker_instance_invalid_cylc_db = TableMaker( non_cylc_eight_db, diff --git a/utils/plot_comparisons/plot_comparisons.py b/utils/plot_comparisons/plot_comparisons.py index 527b254..91e6d51 100755 --- a/utils/plot_comparisons/plot_comparisons.py +++ b/utils/plot_comparisons/plot_comparisons.py @@ -39,6 +39,7 @@ - Supports .nc and f03 ancillary files. - The datasets for comparison must use the same grid coords. """ + import argparse from pathlib import Path